{"openapi":"3.0.3","info":{"title":"Trivia World SaaS - REST API","version":"1.0.0","description":"Official REST API documentation for Trivia World multi-tenant Examination & MCQ SaaS platform.\n\nPrimary client target: Android Mobile App.\nSecondary client target: Web application.","contact":{"name":"Trivia World Engineering"}},"servers":[{"url":"/","description":"Default Base Server"}],"tags":[{"name":"Auth","description":"User authentication, Google OAuth & TOTP 2FA"},{"name":"Student Bootstrap & Profile","description":"Mobile app initialization, identity, and entitlements"},{"name":"Student Devices","description":"Single-device binding and configurable cooldown management"},{"name":"Student Institutions","description":"Institution discovery, join by code, and active switching"},{"name":"Student Practice","description":"Solo self-paced practice and instant explanation"},{"name":"Student Exams","description":"Live proctored exams, anti-cheating, answers, and merit lists"},{"name":"Portal Questions","description":"MCQ question bank, WebP image uploads & bulk CSV/JSON import"},{"name":"Portal Exams","description":"Exam builder, scheduling & submission review"},{"name":"Public Settings","description":"Dynamic brand colors, appearance and site metadata"},{"name":"Super Admin Settings","description":"Platform governance, 3-way storage switcher & anti-abuse"},{"name":"Subscription Packages","description":"Public and Super Admin subscription packages and USD pricing"},{"name":"Cron Jobs","description":"Automated maintenance tasks, session purge and database housekeeping"},{"name":"API Documentation","description":"Interactive documentation and machine-readable exports"},{"name":"Platform Administration","description":"Applications, institutions, purchases and statistics"},{"name":"Portal Operations","description":"Institution overview, batches, team and audit history"},{"name":"Portal Students","description":"Institution student admission and device support"},{"name":"Institution MCP","description":"Owner-managed scoped MCP keys and official MCP Streamable HTTP endpoint"},{"name":"Institutions & Academies","description":"Institution workspace creation and membership"},{"name":"Subjects & Categories","description":"Institution subject management"}],"components":{"securitySchemes":{"CronSecret":{"type":"http","scheme":"bearer","description":"CRON_SECRET value, not a user JWT."},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Provide JWT session token in `Authorization: Bearer <token>` header."},"CookieAuth":{"type":"apiKey","in":"cookie","name":"auth_token","description":"Automatic HttpOnly session cookie issued upon login."}},"schemas":{"SuccessResponse":{"type":"object","description":"Common success envelope. Endpoint-specific fields are described in each operation.","required":["success"],"properties":{"success":{"type":"boolean","example":true}},"additionalProperties":true},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Error description"}}},"User":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"fullName":{"type":"string"},"avatarUrl":{"type":"string","nullable":true},"phoneNumber":{"type":"string","nullable":true},"twoFactorEnabled":{"type":"boolean"},"systemRole":{"type":"string","enum":["SUPER_ADMIN","ADMIN","USER"]}}},"StudentProfile":{"type":"object","properties":{"id":{"type":"string"},"userId":{"type":"string"},"rollNumber":{"type":"string","nullable":true},"targetExam":{"type":"string","nullable":true},"activeInstitutionId":{"type":"string","nullable":true}}},"StudentDevice":{"type":"object","properties":{"id":{"type":"string"},"deviceId":{"type":"string"},"deviceName":{"type":"string"},"appVersion":{"type":"string","nullable":true},"isVerified":{"type":"boolean"},"registeredAt":{"type":"string","format":"date-time"},"lastActiveAt":{"type":"string","format":"date-time"}}},"QuestionOption":{"type":"object","properties":{"key":{"type":"string","enum":["a","b","c","d"]},"text":{"type":"string"},"isCorrect":{"type":"boolean"}}},"Question":{"type":"object","properties":{"id":{"type":"string"},"institutionId":{"type":"string"},"subjectId":{"type":"string"},"questionText":{"type":"string"},"explanation":{"type":"string","nullable":true},"difficulty":{"type":"string","enum":["EASY","MEDIUM","HARD"]},"imageKey":{"type":"string","nullable":true},"imageUrl":{"type":"string","nullable":true},"options":{"type":"array","items":{"$ref":"#/components/schemas/QuestionOption"}}}}}},"paths":{"/api/auth/callback/google":{"get":{"tags":["Auth"],"summary":"Legacy Google callback redirect","description":"Compatibility route that preserves query parameters and redirects to /api/v1/auth/google/callback. New clients should use the versioned callback directly.","security":[],"responses":{"307":{"description":"Redirect to the versioned Google callback","headers":{"Location":{"description":"Versioned callback URL","schema":{"type":"string","format":"uri"}}}}},"operationId":"get_api_auth_callback_google"}},"/api/v1/auth/forgot-password":{"post":{"tags":["Auth"],"summary":"Request a password recovery link","description":"Returns the same success message for known and unknown email addresses. Requires configured SMTP and public site base URL; rate limited to five requests per hour.","security":[],"responses":{"200":{"description":"Generic recovery response","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"required":["success","message"]}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"],"additionalProperties":false}}}},"operationId":"post_api_v1_auth_forgot_password"}},"/api/v1/auth/reset-password":{"post":{"tags":["Auth"],"summary":"Set a new password using a recovery token","description":"One-use recovery token expires after 30 minutes and revokes prior sessions after use; rate limited to ten requests per hour.","security":[],"responses":{"200":{"description":"Password updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"required":["success","message"]}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","pattern":"^[a-f0-9]{64}$"},"password":{"type":"string","minLength":8,"maxLength":128,"writeOnly":true}},"required":["token","password"],"additionalProperties":false}}}},"operationId":"post_api_v1_auth_reset_password"}},"/api/v1/admin/settings/smtp-test":{"post":{"tags":["Super Admin Settings"],"summary":"Test saved SMTP connection","description":"SUPER_ADMIN only. Verifies saved SMTP connection and login without sending mail.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"SMTP connection and login succeeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_admin_settings_smtp_test"}},"/api/v1/admin/applications/{id}":{"delete":{"tags":["Platform Administration"],"summary":"Permanently delete a rejected application","description":"ADMIN or SUPER_ADMIN. Only rejected applications can be deleted, and the exact institution name is required for confirmation.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Rejected application deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"confirmName":{"type":"string"}},"required":["confirmName"],"additionalProperties":false}}}},"operationId":"delete_api_v1_admin_applications_id"}},"/api/v1/portal/team/search":{"get":{"tags":["Portal Operations"],"summary":"Search registered users for staff assignment","description":"Active institution owner or admin only; search by at least five characters of a name or email. Returns up to eight available users with avatars. Rate limited to 60 searches per minute per actor and IP.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Matching registered users","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"users":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"fullName":{"type":"string"},"email":{"type":"string","format":"email"},"avatarUrl":{"type":"string","nullable":true}},"required":[]}}},"required":["success","users"]}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}},{"name":"email","in":"query","required":true,"schema":{"type":"string","minLength":5,"maxLength":254}}],"operationId":"get_api_v1_portal_team_search"}},"/api/v1/portal/batches/{id}":{"patch":{"tags":["Portal Operations"],"summary":"Edit batch details","description":"Active institution owner or admin only.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Updated batch","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"batch":{"type":"object"}},"required":[]}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":120},"code":{"type":"string","maxLength":40},"description":{"type":"string","maxLength":2000}},"required":["name","code","description"],"additionalProperties":false}}}},"operationId":"patch_api_v1_portal_batches_id"},"delete":{"tags":["Portal Operations"],"summary":"Delete an empty batch","description":"Only batches without students or exams may be deleted.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Batch deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"operationId":"delete_api_v1_portal_batches_id"}},"/api/v1/docs":{"get":{"tags":["API Documentation"],"summary":"Open interactive API documentation","description":"Returns the Scalar API reference and browser request console as HTML. Authorization is entered in the UI when calling protected endpoints.","responses":{"200":{"description":"HTML documentation","content":{"text/html":{"schema":{"type":"string"}}}}},"operationId":"get_api_v1_docs"}},"/api/v1/docs/openapi.json":{"get":{"tags":["API Documentation"],"summary":"Download OpenAPI specification","description":"Returns this OpenAPI 3.0.3 document as JSON.","responses":{"200":{"description":"OpenAPI document","content":{"application/json":{"schema":{"type":"object"}}}}},"operationId":"get_api_v1_docs_openapi_json"}},"/api/v1/docs/postman.json":{"get":{"tags":["API Documentation"],"summary":"Download Postman collection","description":"Downloads the checked-in Postman Collection v2.1. Regenerate it with scripts/export-postman-collection.ts after editing the OpenAPI specification.","responses":{"200":{"description":"Postman Collection v2.1","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_docs_postman_json"}},"/api/v1/auth/security":{"get":{"tags":["Auth"],"summary":"Read account security status","description":"Works with a browser cookie or mobile Bearer session. Reports password availability, linked Google sign-in, 2FA state, and remaining recovery codes; never returns secrets.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Security status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"security":{"type":"object","properties":{"hasPassword":{"type":"boolean"},"hasGoogleAccount":{"type":"boolean"},"twoFactorEnabled":{"type":"boolean"},"backupCodesRemaining":{"type":"integer"}},"required":[]}},"required":[]}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_auth_security"}},"/api/v1/auth/password/change":{"post":{"tags":["Auth"],"summary":"Change an existing password","description":"Requires the current password. Available only when the account has a password. Other sessions and password reset links are revoked.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Password changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"currentPassword":{"type":"string","writeOnly":true},"newPassword":{"type":"string","minLength":8,"maxLength":128,"writeOnly":true}},"required":["currentPassword","newPassword"],"additionalProperties":false}}}},"operationId":"post_api_v1_auth_password_change"}},"/api/v1/auth/2fa/setup":{"post":{"tags":["Auth"],"summary":"Set up authenticator two-factor authentication","description":"Authenticated user receives an otpauth QR data URL, URI, and secret for enrollment. The pending secret expires after ten minutes and does not affect the active factor. Keep these values private.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Authenticator enrollment details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"qrCodeDataUrl":{"type":"string"},"otpauthUri":{"type":"string"},"secret":{"type":"string"}},"required":[]}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_auth_2fa_setup"}},"/api/v1/auth/2fa/verify":{"post":{"tags":["Auth"],"summary":"Verify and enable two-factor authentication","description":"Submit the six-digit authenticator code before the pending setup expires. Eight one-use recovery codes are returned once; store them securely.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"2FA enabled with one-time backup codes","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"backupCodes":{"type":"array","items":{"type":"string"}}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","pattern":"^[0-9]{6}$"}},"required":["code"],"additionalProperties":false}}}},"operationId":"post_api_v1_auth_2fa_verify"}},"/api/v1/auth/2fa/disable":{"post":{"tags":["Auth"],"summary":"Disable authenticator two-factor authentication","description":"Requires a current authenticator or unused recovery code; password accounts must also provide currentPassword.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"2FA disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"currentPassword":{"type":"string","writeOnly":true}},"required":["code"],"additionalProperties":false}}}},"operationId":"post_api_v1_auth_2fa_disable"}},"/api/v1/auth/2fa/recovery-codes":{"post":{"tags":["Auth"],"summary":"Replace one-time recovery codes","description":"Requires an authenticator or unused recovery code; password accounts must also provide currentPassword. Invalidates all previous recovery codes and returns eight new codes once.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"New one-time recovery codes","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"backupCodes":{"type":"array","items":{"type":"string"}}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"currentPassword":{"type":"string","writeOnly":true}},"required":["code"],"additionalProperties":false}}}},"operationId":"post_api_v1_auth_2fa_recovery_codes"}},"/api/v1/auth/2fa/complete":{"post":{"tags":["Auth"],"summary":"Finish a 2FA-protected Google sign-in","description":"Web: Google callback sets a short-lived HttpOnly mfa_challenge cookie and redirects to /login?twoFactor=google; submit code here. Mobile: POST /api/v1/auth/google returns requires2FA and challengeToken; submit both here and receive the platform JWT only after verification. A recovery code can replace the six-digit TOTP code. Challenge expires after five minutes or five failed attempts.","security":[],"responses":{"200":{"description":"Full session created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"token":{"type":"string"},"destination":{"type":"string"},"user":{"type":"object"}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"challengeToken":{"type":"string","description":"Required for mobile; web sends the HttpOnly challenge cookie"},"deviceId":{"type":"string"},"deviceName":{"type":"string"},"appVersion":{"type":"string"}},"required":["code"],"additionalProperties":false}}}},"operationId":"post_api_v1_auth_2fa_complete"}},"/api/v1/admin/applications":{"get":{"tags":["Platform Administration"],"summary":"List pending and rejected institution applications","description":"ADMIN or SUPER_ADMIN. Approved applications are excluded. Returns applicants and pagination.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Applications and applicants","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"applications":{"type":"array","items":{"type":"object"}},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"pages":{"type":"integer"}},"required":[]}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"operationId":"get_api_v1_admin_applications"},"post":{"tags":["Platform Administration"],"summary":"Approve or reject an institution application","description":"ADMIN or SUPER_ADMIN. Approval creates an institution and owner membership, subject to the owner's institution creation entitlement.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Application reviewed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"HTTP 422","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"applicationId":{"type":"string"},"action":{"type":"string","enum":["APPROVE","REJECT"]},"reviewNotes":{"type":"string"}},"required":["applicationId","action"],"additionalProperties":false}}}},"operationId":"post_api_v1_admin_applications"}},"/api/v1/admin/institutions":{"get":{"tags":["Platform Administration"],"summary":"List institutions","description":"ADMIN or SUPER_ADMIN. Includes current subscription, plan, quota snapshot and usage counts. Deleted institutions are excluded unless includeDeleted=true.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Institution list","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"institutions":{"type":"array","items":{"type":"object"}}},"required":[]}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"includeDeleted","in":"query","required":false,"schema":{"type":"boolean"}}],"operationId":"get_api_v1_admin_institutions"}},"/api/v1/admin/institutions/{id}":{"get":{"tags":["Platform Administration"],"summary":"View institution details","description":"ADMIN or SUPER_ADMIN. Returns owner identities, location, contact and social fields, subscription and activity counts.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Institution details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"institution":{"type":"object"}},"required":[]}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"operationId":"get_api_v1_admin_institutions_id"},"patch":{"tags":["Platform Administration"],"summary":"Change institution joining code","description":"ADMIN or SUPER_ADMIN. Validates uniqueness and records an audit event.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Joining code updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","pattern":"^[A-Z0-9]{3,10}$"}},"required":["code"],"additionalProperties":false}}}},"operationId":"patch_api_v1_admin_institutions_id"},"delete":{"tags":["Platform Administration"],"summary":"Delete institution access","description":"ADMIN or SUPER_ADMIN. Requires the exact institution name. Soft deletes the institution, bans access, and revokes all MCP keys while preserving payment and audit history.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Institution deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"confirmName":{"type":"string"}},"required":["confirmName"],"additionalProperties":false}}}},"operationId":"delete_api_v1_admin_institutions_id"}},"/api/v1/admin/users/{id}":{"get":{"tags":["Platform Administration"],"summary":"View user details","description":"ADMIN or SUPER_ADMIN. Returns safe account details plus institution memberships and student enrollments. Password hashes and 2FA secrets are never returned.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"User details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"user":{"type":"object"}},"required":[]}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"operationId":"get_api_v1_admin_users_id"},"patch":{"tags":["Platform Administration"],"summary":"Recover account or set system role","description":"ADMIN can reset 2FA or an existing password for USER accounts; SUPER_ADMIN can manage privileged accounts and assign system roles. Actions require a reason, revoke sessions and write an audit event. A generated temporary password is returned once.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Account updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"temporaryPassword":{"type":"string"}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["RESET_2FA","RESET_PASSWORD","SET_ROLE"]},"reason":{"type":"string","minLength":3,"maxLength":500},"role":{"type":"string","enum":["USER","ADMIN","SUPER_ADMIN"]}},"required":["action","reason"],"additionalProperties":false}}}},"operationId":"patch_api_v1_admin_users_id"}},"/api/v1/admin/audit":{"get":{"tags":["Platform Administration"],"summary":"List platform audit records","description":"ADMIN or SUPER_ADMIN. Paginated audit records across platform and institutions. Metadata is allowlisted to avoid exposing credential values.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Audit records and pagination","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"logs":{"type":"array","items":{"type":"object"}},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"pageSize":{"type":"integer"},"total":{"type":"integer"},"pages":{"type":"integer"}},"required":[]}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":120}},{"name":"scope","in":"query","required":false,"schema":{"type":"string","enum":["ALL","PLATFORM","INSTITUTION"]}}],"operationId":"get_api_v1_admin_audit"}},"/api/v1/admin/audit/export":{"get":{"tags":["Platform Administration"],"summary":"Export platform audit records as CSV","description":"ADMIN or SUPER_ADMIN. Exports all matching records in a preset or inclusive custom UTC date range; metadata is allowlisted.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"CSV attachment","content":{"text/csv":{"schema":{"type":"string"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"range","in":"query","required":true,"schema":{"type":"string","enum":["7","30","90","365","CUSTOM"]}},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date"}},{"name":"scope","in":"query","required":false,"schema":{"type":"string","enum":["ALL","PLATFORM","INSTITUTION"]}},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":120}}],"operationId":"get_api_v1_admin_audit_export"}},"/api/v1/admin/audit/retention":{"delete":{"tags":["Super Admin Settings"],"summary":"Purge expired audit logs now","description":"SUPER_ADMIN only. Deletes audit records past the saved institution/system retention periods and writes a new audit event.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Deleted counts and applied policy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"delete_api_v1_admin_audit_retention"}},"/api/v1/admin/purchases":{"get":{"tags":["Platform Administration"],"summary":"List purchase history","description":"ADMIN or SUPER_ADMIN. Paginated payment attempts, including failed and pending attempts. Filters are applied on the server.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Payment attempts and pagination","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"attempts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"provider":{"type":"string"},"status":{"type":"string"},"transactionId":{"type":"string","nullable":true},"providerPaymentId":{"type":"string","nullable":true},"usdCents":{"type":"integer"},"bdtPaisa":{"type":"integer","nullable":true},"durationDays":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"paidAt":{"type":"string","format":"date-time","nullable":true},"institution":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":[]},"plan":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":[]},"user":{"type":"object","properties":{"id":{"type":"string"},"fullName":{"type":"string"},"email":{"type":"string"}},"required":[]}},"required":[]}},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"pages":{"type":"integer"}},"required":[]}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"provider","in":"query","required":false,"schema":{"type":"string","enum":["LEGACY","BKASH","MANUAL"]}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["PENDING","VERIFIED","PAID","FAILED"]}},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":100}}],"operationId":"get_api_v1_admin_purchases"}},"/api/v1/admin/stats":{"get":{"tags":["Platform Administration"],"summary":"Get platform dashboard statistics","description":"ADMIN or SUPER_ADMIN. Returns aggregate counts for users, institutions, seats, questions, pending applications and exams.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Platform totals","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"stats":{"type":"object","properties":{"totalUsers":{"type":"integer"},"totalInstitutions":{"type":"integer"},"activeStudentSeats":{"type":"integer"},"totalQuestions":{"type":"integer"},"pendingApplications":{"type":"integer"},"totalExams":{"type":"integer"}},"required":[]}},"required":[]}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_admin_stats"}},"/api/v1/portal/audit":{"get":{"tags":["Portal Operations"],"summary":"Get institution audit trail","description":"Active institution member or SUPER_ADMIN. Paginated, searchable audit events; type filters targetType. MEMBER matches legacy Member and InstitutionMember, MCP matches InstitutionMcpKey.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Audit events","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"logs":{"type":"array","items":{"type":"object"}},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"pageSize":{"type":"integer"},"total":{"type":"integer"},"pages":{"type":"integer"}},"required":[]}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1}},{"name":"pageSize","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"search","in":"query","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","required":false,"schema":{"type":"string"}}],"operationId":"get_api_v1_portal_audit"}},"/api/v1/portal/audit/export":{"get":{"tags":["Portal Operations"],"summary":"Export own institution audit records as CSV","description":"Active institution manager only. Exports all matching records in a preset or inclusive custom UTC date range.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"CSV attachment","content":{"text/csv":{"schema":{"type":"string"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}},{"name":"range","in":"query","required":true,"schema":{"type":"string","enum":["7","30","90","365","CUSTOM"]}},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date"}},{"name":"type","in":"query","required":false,"schema":{"type":"string"}},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":100}}],"operationId":"get_api_v1_portal_audit_export"}},"/api/v1/portal/overview":{"get":{"tags":["Portal Operations"],"summary":"Get institution dashboard","description":"Requires an active institution membership. Returns plan, quota usage, counts and recent questions.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Institution overview","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"institution":{"type":"object"},"quotas":{"type":"object"},"counts":{"type":"object"},"recentQuestions":{"type":"array","items":{"type":"object"}}},"required":[]}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}}],"operationId":"get_api_v1_portal_overview"}},"/api/v1/portal/batches":{"get":{"tags":["Portal Operations"],"summary":"List institution batches","description":"Returns batches with enrollment and exam counts.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Batches","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"batches":{"type":"array","items":{"type":"object"}}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}}],"operationId":"get_api_v1_portal_batches"},"post":{"tags":["Portal Operations"],"summary":"Create a batch","description":"Active OWNER, ADMIN or TEACHER; QUESTION_EDITOR cannot create batches.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Created batch","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"batch":{"type":"object"}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"institutionId":{"type":"string"},"name":{"type":"string","minLength":2},"code":{"type":"string"},"description":{"type":"string"}},"required":["institutionId","name"],"additionalProperties":false}}}},"operationId":"post_api_v1_portal_batches"}},"/api/v1/portal/team":{"get":{"tags":["Portal Operations"],"summary":"List institution team","description":"Returns institution members and their user profiles.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Team members","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"members":{"type":"array","items":{"type":"object"}}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}}],"operationId":"get_api_v1_portal_team"},"post":{"tags":["Portal Operations"],"summary":"Invite a registered user to the team","description":"Active OWNER or ADMIN. Creates PENDING membership until the invited user accepts. Rejected invitations can be sent again. Only OWNER can invite ADMIN. Rate limited to 20 per hour.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Invitation created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"institutionId":{"type":"string"},"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["ADMIN","TEACHER","QUESTION_EDITOR"]}},"required":["institutionId","email","role"],"additionalProperties":false}}}},"operationId":"post_api_v1_portal_team"},"patch":{"tags":["Portal Operations"],"summary":"Edit team member role or status","description":"Active OWNER can manage non-owner members; active ADMIN can manage teachers and editors but cannot assign or manage admins. PENDING and REJECTED invitations cannot be activated by PATCH; acceptance belongs to the invitee.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Member updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"memberId":{"type":"string"},"role":{"type":"string","enum":["ADMIN","TEACHER","QUESTION_EDITOR"]},"status":{"type":"string","enum":["ACTIVE","SUSPENDED"]}},"required":["memberId"],"additionalProperties":false}}}},"operationId":"patch_api_v1_portal_team"},"delete":{"tags":["Portal Operations"],"summary":"Remove team member","description":"Active OWNER can remove non-owner members; active ADMIN can remove teachers and editors. Their MCP keys are deleted by database cascade.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Member removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request conflicts with the current resource state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"memberId":{"type":"string"}},"required":["memberId"],"additionalProperties":false}}}},"operationId":"delete_api_v1_portal_team"}},"/api/v1/portal/institutions/{id}":{"patch":{"tags":["Institutions & Academies"],"summary":"Update institution profile and learning permissions","description":"Active OWNER or ADMIN can change profile, branding, contact details, allowPracticeMode and allowStudyMode. Both permission flags default true; disabling either takes effect on the next student API request.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Institution profile saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"description":{"type":"string","nullable":true,"maxLength":2000},"primaryColor":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"accentColor":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"allowPracticeMode":{"type":"boolean"},"allowStudyMode":{"type":"boolean"},"address":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"contactEmail":{"type":"string","nullable":true},"contactPhone":{"type":"string","nullable":true},"websiteUrl":{"type":"string","nullable":true},"facebookUrl":{"type":"string","nullable":true},"instagramUrl":{"type":"string","nullable":true},"youtubeUrl":{"type":"string","nullable":true},"linkedinUrl":{"type":"string","nullable":true}},"required":["description","primaryColor","accentColor"],"additionalProperties":false}}}},"operationId":"patch_api_v1_portal_institutions_id"}},"/api/v1/portal/institutions/{id}/learning-permissions":{"patch":{"tags":["Institutions & Academies"],"summary":"Switch student learning permissions immediately","description":"Active OWNER or ADMIN only. Send allowPracticeMode or allowStudyMode as a boolean; omitted permissions and institution profile fields remain unchanged. Student APIs enforce the saved value on their next request.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Saved learning permissions","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"institution":{"type":"object","properties":{"id":{"type":"string"},"allowPracticeMode":{"type":"boolean"},"allowStudyMode":{"type":"boolean"}},"required":[]}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"allowPracticeMode":{"type":"boolean"},"allowStudyMode":{"type":"boolean"}},"required":[],"additionalProperties":false}}}},"operationId":"patch_api_v1_portal_institutions_id_learning_permissions"}},"/api/v1/student/batches":{"get":{"tags":["Student Practice"],"summary":"List active assigned batches and subjects","description":"Returns batches from the authenticated student's active enrollments only. Each batch includes institution allowPracticeMode and allowStudyMode flags and active question counts for subjects assigned to that batch. No-store response.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Assigned batches","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"batches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"institution":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"allowPracticeMode":{"type":"boolean"},"allowStudyMode":{"type":"boolean"}},"required":[]},"subjects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"code":{"type":"string","nullable":true},"totalQuestions":{"type":"integer"}},"required":[]}}},"required":[]}}},"required":["success","batches"]}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_student_batches"}},"/api/v1/student/subjects/{id}/study":{"get":{"tags":["Student Practice"],"summary":"Read assigned subject MCQs","description":"Requires active enrollment and batch assignment to this subject in the same institution. Returns active questions, answer keys and explanations. Institution allowStudyMode=false returns 403. Supports paginated text search and difficulty filtering; no-store response.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Paginated MCQs with correct answers and explanations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":50,"default":20}},{"name":"difficulty","in":"query","required":false,"schema":{"type":"string","enum":["ALL","EASY","MEDIUM","HARD"]}},{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":100}}],"operationId":"get_api_v1_student_subjects_id_study"}},"/api/v1/portal/integrations/mcp":{"get":{"tags":["Institution MCP"],"summary":"List MCP keys and active key allowance","description":"Active institution OWNER or ADMIN. Key tokens are never returned after creation. MCP is active only on a paid plan with the feature enabled. used counts active, non-revoked keys; the package keyLimit is concurrent, not monthly.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"MCP entitlement and active keys","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"enabled":{"type":"boolean"},"keyLimit":{"type":"integer"},"used":{"type":"integer"},"keys":{"type":"array","items":{"type":"object"}},"sampleUrl":{"type":"string"},"tutorialVideoId":{"type":"string"}},"required":[]}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}}],"operationId":"get_api_v1_portal_integrations_mcp"},"post":{"tags":["Institution MCP"],"summary":"Create a scoped MCP key","description":"Active OWNER or ADMIN. Cannot exceed the subscription's active MCP key limit. Revoke an existing key to free a slot. Token is stored as a SHA-256 hash and the URL containing its secret is returned once. The key is bound to an active team member, permitted tools, and optionally subjects. Teacher and editor keys require explicit subjects.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"201":{"description":"One-time MCP URL","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"key":{"type":"object"},"url":{"type":"string"}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request conflicts with the current resource state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"institutionId":{"type":"string"},"memberId":{"type":"string"},"name":{"type":"string","minLength":2,"maxLength":60},"permissions":{"type":"array","items":{"type":"string","enum":["subjects:read","questions:read","questions:create","questions:edit","exams:create","reports:read"]}},"allowedSubjectIds":{"type":"array","items":{"type":"string"}}},"required":["institutionId","memberId","name","permissions"],"additionalProperties":false}}}},"operationId":"post_api_v1_portal_integrations_mcp"},"patch":{"tags":["Institution MCP"],"summary":"Edit MCP key permissions","description":"Active OWNER or ADMIN. Updates key name, permissions and subject scope without changing the token or bound member. Member must still be active; key must not be revoked.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Key permissions updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"institutionId":{"type":"string"},"keyId":{"type":"string"},"name":{"type":"string","minLength":2,"maxLength":60},"permissions":{"type":"array","items":{"type":"string","enum":["subjects:read","questions:read","questions:create","questions:edit","exams:create","reports:read"]}},"allowedSubjectIds":{"type":"array","items":{"type":"string"}}},"required":["institutionId","keyId","name","permissions"],"additionalProperties":false}}}},"operationId":"patch_api_v1_portal_integrations_mcp"},"delete":{"tags":["Institution MCP"],"summary":"Revoke an MCP key","description":"Active OWNER or ADMIN. Revocation takes effect on the next MCP request.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Key revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"institutionId":{"type":"string"},"keyId":{"type":"string"}},"required":["institutionId","keyId"],"additionalProperties":false}}}},"operationId":"delete_api_v1_portal_integrations_mcp"}},"/api/v1/integrations/mcp/{token}":{"post":{"tags":["Institution MCP"],"summary":"MCP Streamable HTTP request","description":"Official MCP SDK endpoint. Supply a one-time URL issued by the owner; the path token authenticates an institution/member/permission/subject scope on every request. Supports MCP initialization, tools/list and tools/call. Tools: list_subjects, list_questions, create_questions, edit_questions, schedule_exam, subject_report, student_results_summary. The create_questions and edit_questions tools each accept a questions array of 1–50 items; edits require questionId per item. Batches are atomic and limited to 256 KiB per request. Question creation uses the plan's question bank capacity; there is no separate monthly MCP creation quota. Use application/json and Accept: application/json, text/event-stream as required by MCP clients.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"MCP JSON-RPC request or envelope; follow the negotiated protocol revision."},"examples":{"createQuestions":{"summary":"Create MCQs in one request","value":{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"create_questions","arguments":{"questions":[{"subjectId":"SUBJECT_ID","questionText":"What is two plus two?","options":[{"text":"4"},{"text":"5"},{"text":"6"},{"text":"7"}],"correctOption":"a"}]}}}},"editQuestions":{"summary":"Edit MCQs in one request","value":{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"edit_questions","arguments":{"questions":[{"questionId":"QUESTION_ID","subjectId":"SUBJECT_ID","questionText":"What is three plus three?","options":[{"text":"6"},{"text":"5"},{"text":"7"},{"text":"8"}],"correctOption":"a"}]}}}}}}}},"responses":{"200":{"description":"MCP JSON-RPC response","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_integrations_mcp_token"},"get":{"tags":["Institution MCP"],"summary":"MCP stream or session request","description":"MCP protocol GET entry. Legacy stateless clients receive 405. The path token is checked on each request.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"MCP response"},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"405":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_integrations_mcp_token"},"delete":{"tags":["Institution MCP"],"summary":"MCP session termination","description":"MCP protocol session DELETE entry; this does not revoke the key. Use the owner portal DELETE endpoint to revoke.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"MCP response"},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"405":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"delete_api_v1_integrations_mcp_token"}},"/api/v1/cron/expire-subscriptions":{"get":{"tags":["Cron Jobs"],"summary":"Expire elapsed subscriptions","description":"Daily Vercel cron. Requires Authorization: Bearer CRON_SECRET. Marks elapsed ACTIVE or GRACE_PERIOD subscriptions EXPIRED; questions, students, payments and results are preserved. Entitlement checks use expiresAt immediately, even before cron runs.","security":[{"CronSecret":[]}],"responses":{"200":{"description":"Expired subscription count","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_cron_expire_subscriptions"},"post":{"tags":["Cron Jobs"],"summary":"Run subscription expiry maintenance","description":"Same protected maintenance operation as GET, for external schedulers.","security":[{"CronSecret":[]}],"responses":{"200":{"description":"Expired subscription count","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_cron_expire_subscriptions"}},"/api/v1/cron/cleanup-audit-logs":{"get":{"tags":["Cron Jobs"],"summary":"Permanently delete expired audit logs","description":"Daily maintenance. Requires Authorization: Bearer CRON_SECRET. Uses separate saved retention periods for institution and system audit rows.","security":[{"CronSecret":[]}],"responses":{"200":{"description":"Deleted counts and applied policy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_cron_cleanup_audit_logs"},"post":{"tags":["Cron Jobs"],"summary":"Run audit cleanup from an external scheduler","description":"Same protected cleanup operation as GET.","security":[{"CronSecret":[]}],"responses":{"200":{"description":"Deleted counts and applied policy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_cron_cleanup_audit_logs"}},"/api/v1/cron/cleanup-exam-history":{"get":{"tags":["Cron Jobs"],"summary":"Purge expired exam history","description":"Daily maintenance. Requires Authorization: Bearer CRON_SECRET. Deletes finished exams after their retentionDays elapsed, including attempts, scores, question snapshots and batch assignments. Retention is captured when an exam is published. The question bank remains.","security":[{"CronSecret":[]}],"responses":{"200":{"description":"Deleted count and batch status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_cron_cleanup_exam_history"},"post":{"tags":["Cron Jobs"],"summary":"Run exam history retention cleanup","description":"Same protected cleanup operation as GET for external schedulers.","security":[{"CronSecret":[]}],"responses":{"200":{"description":"Deleted count and batch status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_cron_cleanup_exam_history"}},"/api/v1/portal/billing/attempt":{"get":{"tags":["Subscription Packages"],"summary":"Get own payment attempt status","description":"Returns the authenticated purchaser's payment attempt. Read-only; does not reconcile the gateway. No-store response.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Current status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"attempt":{"type":"object","properties":{"id":{"type":"string"},"institutionId":{"type":"string"},"institutionName":{"type":"string"},"planName":{"type":"string"},"provider":{"type":"string","enum":["BKASH","MANUAL","LEGACY"]},"status":{"type":"string","enum":["PENDING","VERIFIED","PAID","FAILED"]},"amountBdt":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"paidAt":{"type":"string","format":"date-time","nullable":true}},"required":[]}},"required":[]}}}}},"parameters":[{"name":"attemptId","in":"query","required":true,"schema":{"type":"string"}}],"operationId":"get_api_v1_portal_billing_attempt"},"post":{"tags":["Subscription Packages"],"summary":"Recheck a bKash payment","description":"Rate limited to five checks per minute. Server reconciles bKash payment before returning status. Manual transactions cannot be rechecked here.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Reconciled status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"attempt":{"type":"object","properties":{"id":{"type":"string"},"institutionId":{"type":"string"},"institutionName":{"type":"string"},"planName":{"type":"string"},"provider":{"type":"string","enum":["BKASH","MANUAL","LEGACY"]},"status":{"type":"string","enum":["PENDING","VERIFIED","PAID","FAILED"]},"amountBdt":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"paidAt":{"type":"string","format":"date-time","nullable":true}},"required":[]}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"attemptId":{"type":"string"}},"required":["attemptId"],"additionalProperties":false}}}},"operationId":"post_api_v1_portal_billing_attempt"}},"/api/v1/portal/exams":{"get":{"tags":["Portal Exams"],"summary":"List institution exams by period","description":"Active institution members. UTC period views: scheduled includes drafts and exams whose end time has not passed; this_month, last_month and year include finished published or archived exams. Returns 18 per page, available years, and the selected year's bulk-clear eligible count.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Exams, pagination and clear eligibility","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"exams":{"type":"array","items":{"type":"object"}},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"pageSize":{"type":"integer"},"total":{"type":"integer"},"pages":{"type":"integer"}},"required":[]},"years":{"type":"array","items":{"type":"integer"}},"clearEligibleCount":{"type":"integer"},"serverTime":{"type":"string","format":"date-time"}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}},{"name":"view","in":"query","required":false,"schema":{"type":"string","enum":["scheduled","this_month","last_month","year"],"default":"scheduled"}},{"name":"year","in":"query","required":false,"schema":{"type":"integer","minimum":2000,"maximum":2100}},{"name":"subjectId","in":"query","required":false,"schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["newest","oldest","subject","title"],"default":"newest"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}}],"operationId":"get_api_v1_portal_exams"},"post":{"tags":["Portal Exams"],"summary":"Create an exam","description":"Active institution OWNER, ADMIN or TEACHER. Exam starts as a draft; duration defaults to 30 minutes. Only OWNER or ADMIN may set appOnly=true.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Created exam","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"exam":{"type":"object"}},"required":[]}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"institutionId":{"type":"string"},"title":{"type":"string","minLength":2},"subjectId":{"type":"string","nullable":true},"durationMinutes":{"type":"integer","minimum":1,"maximum":1440},"timerMode":{"type":"string","enum":["EXAM","QUESTION"]},"secondsPerQuestion":{"type":"integer","minimum":5,"maximum":3600,"nullable":true},"marksPerCorrect":{"type":"number","minimum":0.01},"negativeMarksPerWrong":{"type":"number","minimum":0},"maxAttempts":{"type":"integer","minimum":1,"maximum":100},"resultPolicy":{"type":"string","enum":["IMMEDIATE","AFTER_EXAM","MANUAL"]},"resultVisibility":{"type":"string","enum":["NONE","SCORE","REVIEW"]},"tabSwitchAllowed":{"type":"boolean"},"appOnly":{"type":"boolean","description":"OWNER or ADMIN only. When true, website students cannot list, start or continue the exam."},"startAt":{"type":"string","format":"date-time"},"endAt":{"type":"string","format":"date-time"}},"required":["institutionId","title"],"additionalProperties":false}}}},"operationId":"post_api_v1_portal_exams"},"delete":{"tags":["Portal Exams"],"summary":"Clear finished exams in a year","description":"Active institution OWNER, ADMIN or TEACHER. Permanently deletes finished published or archived exams in the selected UTC year only when they ended before the current UTC month. Current-month, future, live and draft exams are excluded even if requested directly. Cascades to attempts, results, snapshots and batch links; leaves question bank records.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Deleted exam count","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"institutionId":{"type":"string"},"year":{"type":"integer","minimum":2000,"maximum":2100},"confirm":{"type":"string","enum":["DELETE"]}},"required":["institutionId","year","confirm"],"additionalProperties":false}}}},"operationId":"delete_api_v1_portal_exams"}},"/api/v1/portal/exams/{id}":{"get":{"tags":["Portal Exams"],"summary":"Get an exam","description":"Returns exam, assigned batches, questions with resolved imageUrl, and attempt count to an active member.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Exam details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"exam":{"type":"object"}},"required":[]}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"operationId":"get_api_v1_portal_exams_id"},"put":{"tags":["Portal Exams"],"summary":"Update an exam","description":"Active OWNER, ADMIN or TEACHER. Only OWNER or ADMIN may change appOnly. Only supplied fields are changed. After the first attempt, only end-time extensions and result policies may change. Save settings before publishing; publishing requires at least one assigned batch.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Updated exam","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":2},"subjectId":{"type":"string","nullable":true},"durationMinutes":{"type":"integer","minimum":1,"maximum":1440},"timerMode":{"type":"string","enum":["EXAM","QUESTION"]},"secondsPerQuestion":{"type":"integer","minimum":5,"maximum":3600,"nullable":true},"marksPerCorrect":{"type":"number","minimum":0.01},"negativeMarksPerWrong":{"type":"number","minimum":0},"maxAttempts":{"type":"integer","minimum":1,"maximum":100},"resultPolicy":{"type":"string","enum":["IMMEDIATE","AFTER_EXAM","MANUAL"]},"resultVisibility":{"type":"string","enum":["NONE","SCORE","REVIEW"]},"tabSwitchAllowed":{"type":"boolean"},"appOnly":{"type":"boolean","description":"OWNER or ADMIN only. When true, website students cannot list, start or continue the exam."},"startAt":{"type":"string","format":"date-time"},"endAt":{"type":"string","format":"date-time"},"description":{"type":"string","nullable":true},"instructions":{"type":"string","nullable":true},"shuffleQuestions":{"type":"boolean"},"shuffleOptions":{"type":"boolean"},"status":{"type":"string","enum":["DRAFT","PUBLISHED","ARCHIVED"]}},"required":[],"additionalProperties":false}}}},"operationId":"put_api_v1_portal_exams_id"},"delete":{"tags":["Portal Exams"],"summary":"Permanently delete one finished exam","description":"Active institution OWNER, ADMIN or TEACHER. Exam must be published or archived and its end time must have passed. Deletes attempts, scores, snapshots and batch assignments; leaves question bank records.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Exam history deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"confirm":{"type":"string","enum":["DELETE"]}},"required":["confirm"],"additionalProperties":false}}}},"operationId":"delete_api_v1_portal_exams_id"}},"/api/v1/portal/exams/{id}/batches":{"post":{"tags":["Portal Exams"],"summary":"Replace exam batch assignments","description":"An empty batchIds array removes student eligibility on a draft; published exams must retain at least one assigned batch. Active OWNER, ADMIN or TEACHER.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Assignments updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"batchIds":{"type":"array","items":{"type":"string"}}},"required":["batchIds"],"additionalProperties":false}}}},"operationId":"post_api_v1_portal_exams_id_batches"}},"/api/v1/portal/exams/{id}/questions":{"post":{"tags":["Portal Exams"],"summary":"Manage exam questions","description":"SET replaces the selected question set atomically and accepts an empty questionIds array; ADD appends questionIds; REMOVE requires questionId; RANDOMIZE requires count and optionally subjectId. Plan question cap is enforced.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Question set updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request conflicts with the current resource state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["ADD","REMOVE","RANDOMIZE","SET"]},"questionIds":{"type":"array","items":{"type":"string"},"maxItems":200},"questionId":{"type":"string"},"count":{"type":"integer","minimum":1,"maximum":200},"subjectId":{"type":"string"}},"required":["action"],"additionalProperties":false}}}},"operationId":"post_api_v1_portal_exams_id_questions"}},"/api/v1/portal/exams/{id}/results":{"get":{"tags":["Portal Exams"],"summary":"Get exam submissions and ranking","description":"Active institution member or SUPER_ADMIN. Returns exam summary and ranked submitted attempts within maxAttempts per student. Legacy excess attempts remain stored for audit but are excluded from official merit.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Exam result summary","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"exam":{"type":"object"},"summary":{"type":"object"},"submissions":{"type":"array","items":{"type":"object"}}},"required":[]}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"operationId":"get_api_v1_portal_exams_id_results"}},"/api/v1/portal/exams/{id}/submissions/{attemptId}":{"get":{"tags":["Portal Exams"],"summary":"Review one submitted attempt","description":"Active institution member or SUPER_ADMIN. Includes question imageUrl, selected answers, correct answers, explanation and marks per question.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Attempt review","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"attempt":{"type":"object"},"review":{"type":"array","items":{"type":"object"}}},"required":[]}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}}],"operationId":"get_api_v1_portal_exams_id_submissions_attemptId"}},"/api/v1/portal/questions/{id}":{"delete":{"tags":["Portal Questions"],"summary":"Delete a question","description":"Requires active institution membership. Deletion lock can return QUESTION_DELETION_LOCKED with daysRemaining and unlockDate.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Question deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Permission denied or question deletion is locked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"operationId":"delete_api_v1_portal_questions_id"},"get":{"tags":["Portal Questions"],"summary":"Get question for editing","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Question, options, image URL, and deletion policy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Institution membership required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_portal_questions_id","description":"Get question for editing"},"patch":{"tags":["Portal Questions"],"summary":"Edit question and replace or remove image","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"description":"Requires active institution membership. Validates subject and exactly four options with one correct answer, then reshuffles options. A replaced or removed image is deleted from storage after the question is saved.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subjectId","questionText","difficulty","imageKey","options"],"properties":{"subjectId":{"type":"string"},"questionText":{"type":"string"},"explanation":{"type":"string","nullable":true},"difficulty":{"type":"string","enum":["EASY","MEDIUM","HARD"]},"imageKey":{"type":"string","nullable":true},"options":{"type":"array","minItems":4,"maxItems":4,"items":{"type":"object","required":["key","text","isCorrect"],"properties":{"key":{"type":"string"},"text":{"type":"string"},"isCorrect":{"type":"boolean"}}}}}}}}},"responses":{"200":{"description":"Question updated; imageCleanupFailed flags storage cleanup errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request conflicts with the current resource state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"patch_api_v1_portal_questions_id"}},"/api/v1/student/device":{"get":{"tags":["Student Devices"],"summary":"Get active student device","description":"Returns current binding and cooldown policy. Legacy singular endpoint; the /student/devices collection also exists.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Active device and cooldown","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"activeDevice":{"type":"object","nullable":true},"cooldownPolicy":{"type":"object","properties":{"cooldownDays":{"type":"integer"},"canReset":{"type":"boolean"},"cooldownRemainingHours":{"type":"integer"}},"required":[]}},"required":[]}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_student_device"},"post":{"tags":["Student Devices"],"summary":"Bind or verify student device","description":"May return DEVICE_COOLDOWN_LOCKED when attempting a change during the configured cooldown. Android should reuse a stable app-scoped device ID across reinstalls.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Device bound or verified","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"deviceId":{"type":"string","minLength":3},"deviceName":{"type":"string","minLength":2},"appVersion":{"type":"string"}},"required":["deviceId","deviceName"],"additionalProperties":false}}}},"operationId":"post_api_v1_student_device"},"delete":{"tags":["Student Devices"],"summary":"Revoke current student device","description":"Cooldown policy may prevent reset; returns 403 until reset is allowed.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Device revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"delete_api_v1_student_device"}},"/api/v1/student/profile":{"get":{"tags":["Student Bootstrap & Profile"],"summary":"Get student profile","description":"Returns identity, 2FA status, current device, active student enrollments, staff memberships, and activity counts.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Student profile","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"profile":{"type":"object"},"device":{"type":"object","nullable":true},"enrollments":{"type":"array","items":{"type":"object"}},"staffMemberships":{"type":"array","items":{"type":"object"}},"stats":{"type":"object"}},"required":[]}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_student_profile"},"put":{"tags":["Student Bootstrap & Profile"],"summary":"Update student profile","description":"Only provided fields are changed. Student may set rollNumber for the active institution once when empty; after that only institution staff can change or clear it. Roll values are trimmed, at most 50 characters, and cannot contain control characters. targetExam and phoneNumber may be cleared with null.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Updated student profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"fullName":{"type":"string","minLength":2},"rollNumber":{"type":"string","maxLength":50,"nullable":true},"targetExam":{"type":"string","nullable":true},"phoneNumber":{"type":"string","nullable":true}},"required":[],"additionalProperties":false}}}},"operationId":"put_api_v1_student_profile"}},"/api/v1/student/profile/avatar":{"post":{"tags":["Student Bootstrap & Profile"],"summary":"Upload profile photo","description":"Authenticated multipart upload (file). JPEG, PNG or WebP up to 2 MB; server crops to 256x256 WebP. Rate limited to six per hour.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Updated avatar URL","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"avatarUrl":{"type":"string"}},"required":[]}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"operationId":"post_api_v1_student_profile_avatar"}},"/api/v1/student/invitations":{"get":{"tags":["Student Bootstrap & Profile"],"summary":"List pending staff invitations","description":"Only invitations addressed to the authenticated user for available institutions are returned.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Pending invitations","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"invitations":{"type":"array","items":{"type":"object"}}},"required":[]}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_student_invitations"}},"/api/v1/student/invitations/{id}/respond":{"post":{"tags":["Student Bootstrap & Profile"],"summary":"Accept or reject staff invitation","description":"Only the invited user can answer a PENDING invitation. Acceptance grants staff access; rejection records the refusal. Both changes are audited.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Updated membership","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"member":{"type":"object"}},"required":[]}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["ACCEPT","REJECT"]}},"required":["action"],"additionalProperties":false}}}},"operationId":"post_api_v1_student_invitations_id_respond"}},"/api/v1/student/institutions/{id}/roll":{"patch":{"tags":["Student Institutions"],"summary":"Set an empty institution roll number","description":"The authenticated student can set their own PENDING or ACTIVE institution enrollment roll once, only while it is empty. Roll values are trimmed, 1–50 characters, and cannot contain control characters. Institution staff can change it later via PATCH /portal/students.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Institution roll number saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rollNumber":{"type":"string","minLength":1,"maxLength":50}},"required":["rollNumber"],"additionalProperties":false}}}},"operationId":"patch_api_v1_student_institutions_id_roll"}},"/api/v1/portal/students/device":{"delete":{"tags":["Portal Students"],"summary":"Reset an enrolled student's device lock","description":"Active institution OWNER, ADMIN or TEACHER may reset an ACTIVE student's account-wide device binding only when the actor is not the student and both the student's approved enrollment and the staff membership predate the active device binding. This does not remove the enrollment or seat lock. The student binds a device on their next app sign-in.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Device lock reset or already clear","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enrollmentId":{"type":"string"}},"required":["enrollmentId"],"additionalProperties":false}}}},"operationId":"delete_api_v1_portal_students_device"}},"/api/v1/student/overview":{"get":{"tags":["Student Bootstrap & Profile"],"summary":"Get student dashboard","description":"Returns practice statistics, current enrollments, pending join requests and recent attempts. Banned institutions are excluded.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Student dashboard","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"profile":{"type":"object"},"stats":{"type":"object"},"enrollments":{"type":"array","items":{"type":"object"}},"pendingRequests":{"type":"array","items":{"type":"object"}},"recentAttempts":{"type":"array","items":{"type":"object"}}},"required":[]}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_student_overview"}},"/api/v1/portal/batches/{id}/subjects":{"get":{"tags":["Portal Operations"],"summary":"List subjects linked to a batch","description":"Requires active institution membership.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Linked subjects","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"subjects":{"type":"array","items":{"type":"object"}}},"required":[]}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"operationId":"get_api_v1_portal_batches_id_subjects"},"put":{"tags":["Portal Operations"],"summary":"Replace batch subjects","description":"Accepts subjects belonging to the same institution. Cannot remove a subject required by a published exam.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Batch subjects updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subjectIds":{"type":"array","items":{"type":"string"}}},"required":["subjectIds"],"additionalProperties":false}}}},"operationId":"put_api_v1_portal_batches_id_subjects"}},"/api/v1/portal/exams/{id}/validate":{"post":{"tags":["Portal Exams"],"summary":"Validate exam before publishing","description":"Checks schedule, selected subject, questions, at least one assigned batch and batch subject links without changing the exam.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Ready flag and validation issues","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"ready":{"type":"boolean"},"issues":{"type":"array","items":{"type":"string"}}},"required":[]}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"operationId":"post_api_v1_portal_exams_id_validate"}},"/api/v1/portal/exams/{id}/release-results":{"post":{"tags":["Portal Exams"],"summary":"Release or hide manually published results","description":"OWNER, ADMIN or TEACHER. Exam must be published with MANUAL result policy and SCORE or REVIEW visibility. Writes an institution audit log.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Result release updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"released":{"type":"boolean"}},"required":["released"],"additionalProperties":false}}}},"operationId":"post_api_v1_portal_exams_id_release_results"}},"/api/v1/student/exams/{id}":{"get":{"tags":["Student Exams"],"summary":"Get assigned exam briefing before starting","description":"Returns schedule, serverTime, state, instructions, question count, scoring, timer, attempt, result and proctoring rules without creating an attempt or exposing answer keys. Show this briefing and require an explicit student action before calling start. Unassigned, unpublished and app-only exams requested from the web return 404.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Trivia-Client","in":"header","description":"Use android for app requests; omit or use web for dashboard requests. This marker alone is not proof of app identity.","schema":{"type":"string","enum":["android","web"]}},{"name":"X-Trivia-Device-Id","in":"header","description":"Registered active student device ID, required with a Bearer token for app-only exams.","schema":{"type":"string"}}],"responses":{"200":{"description":"Exam briefing and serverTime","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Exam unavailable to this student or client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_student_exams_id"}},"/api/v1/student/exams/{id}/advance":{"post":{"tags":["Student Exams"],"summary":"Advance per-question timer","description":"Only for QUESTION timer mode. Marks an expired current question as timed out, then moves to the next question or submits the attempt.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Next question deadline or submission state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"attemptId":{"type":"string"}},"required":["attemptId"],"additionalProperties":false}}}},"operationId":"post_api_v1_student_exams_id_advance"}},"/api/v1/student/notification-topics":{"get":{"tags":["Student Institutions"],"summary":"List authorized notification topics","description":"Android calls this after login, on app launch, and after token or enrollment changes. The app subscribes and unsubscribes locally. Returns all-students, active institution, and assigned batch topics; no device identifier is stored.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_student_notification_topics"},"post":{"tags":["Student Institutions"],"summary":"Synchronize this Firebase registration's topics","description":"Web push: send the browser FCM registration ID to reconcile server-side subscriptions; this requires Firebase topic-management IAM permissions. Older Android clients marked X-Trivia-Client: android receive only the authorized topic list and subscribe locally. The identifier is used only during this request.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"registrationId":{"type":"string","minLength":16,"maxLength":4096}},"required":["registrationId"],"additionalProperties":false}}}},"operationId":"post_api_v1_student_notification_topics"},"delete":{"tags":["Student Institutions"],"summary":"Remove this browser's notification topics","description":"Web push: call before logout or account switch with the locally held FCM registration ID. Android unsubscribes locally; older Android clients marked X-Trivia-Client: android receive a no-op success.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"registrationId":{"type":"string","minLength":16,"maxLength":4096}},"required":["registrationId"],"additionalProperties":false}}}},"operationId":"delete_api_v1_student_notification_topics"}},"/api/v1/student/batches/{id}/leave":{"post":{"tags":["Student Institutions"],"summary":"Leave an assigned batch","description":"Student leaves their own active batch assignment. Resync notification topics after success.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"operationId":"post_api_v1_student_batches_id_leave"}},"/api/v1/student/institutions/{id}/leave":{"post":{"tags":["Student Institutions"],"summary":"Leave an institution","description":"Student leaves their own active institution enrollment and all its batches. Resync notification topics after success.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"operationId":"post_api_v1_student_institutions_id_leave"}},"/api/v1/admin/notifications":{"post":{"tags":["Platform Administration"],"summary":"Notify all students","description":"ADMIN or SUPER_ADMIN only. Optional HTTPS image URL and destination type none, internal, website, or play_store. Never include private student data.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"message":{"type":"string"},"imageUrl":{"type":"string"},"destination":{"type":"object"}},"required":["title","message","destination"],"additionalProperties":false}}}},"operationId":"post_api_v1_admin_notifications"}},"/api/v1/portal/notifications":{"get":{"tags":["Portal Operations"],"summary":"List notification audiences","description":"Institution OWNER or ADMIN only. Returns batches and the institution notification switch.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}}],"operationId":"get_api_v1_portal_notifications"},"post":{"tags":["Portal Operations"],"summary":"Notify institution or batch students","description":"Institution OWNER or ADMIN only. Optional batchId and HTTPS image URL. No custom destination allowed. Disabled institution switch blocks sending.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"institutionId":{"type":"string"},"batchId":{"type":"string"},"title":{"type":"string"},"message":{"type":"string"},"imageUrl":{"type":"string"}},"required":["institutionId","title","message"],"additionalProperties":false}}}},"operationId":"post_api_v1_portal_notifications"}},"/api/v1/portal/institutions/{id}/notification-permission":{"patch":{"tags":["Portal Operations"],"summary":"Set institution notification switch","description":"Only an active institution OWNER may change this setting. When disabled, manual and automatic institution notifications are blocked.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"notificationsEnabled":{"type":"boolean"}},"required":["notificationsEnabled"],"additionalProperties":false}}}},"operationId":"patch_api_v1_portal_institutions_id_notification_permission"}},"/api/v1/cron/publish-exam-result-notifications":{"get":{"tags":["Cron Jobs"],"summary":"Notify batches when automatic exam results become available","security":[{"CronSecret":[]}],"responses":{"200":{"description":"Automatic result notification pass","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Request rejected; response contains success=false and a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_cron_publish_exam_result_notifications","description":"Notify batches when automatic exam results become available"}},"/api/v1/auth/register":{"post":{"tags":["Auth"],"summary":"Register new user account","description":"Creates global user identity and default student profile.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password","fullName"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8},"fullName":{"type":"string"},"targetExam":{"type":"string","example":"BCS / Medical"}}}}}},"responses":{"201":{"description":"User registered successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"token":{"type":"string"},"user":{"$ref":"#/components/schemas/User"}}}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request conflicts with the current resource state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_auth_register"}},"/api/v1/auth/login":{"post":{"tags":["Auth"],"summary":"Login with email and password","description":"Authenticates credentials and handles 2FA challenges. Android clients should send a deviceId stable across reinstalls with the same app signing key and a human-readable deviceName. Device binding is scoped to the student account: one active device per student, with a cooldown before switching. Institution staff can reset a student's binding from their portal.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string"},"twoFactorCode":{"type":"string","description":"Six-digit TOTP or one-use recovery code if 2FA is active"},"deviceId":{"type":"string","description":"Stable app-scoped Android device ID, reused on later logins and in X-Trivia-Device-Id for app-only exams"},"deviceName":{"type":"string","description":"Human-readable device model"},"appVersion":{"type":"string","description":"Optional Android app version"}}}}}},"responses":{"200":{"description":"Login successful or 2FA required","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"requires2FA":{"type":"boolean"},"token":{"type":"string"},"user":{"$ref":"#/components/schemas/User"}}}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service unavailable or not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_auth_login"}},"/api/v1/auth/me":{"get":{"tags":["Auth"],"summary":"Get current authenticated user session","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Current user profile and multi-tenant memberships","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_auth_me","description":"Get current authenticated user session"}},"/api/v1/auth/logout":{"post":{"tags":["Auth"],"summary":"Log out and permanently revoke database session","description":"Permanently deletes the active session token from the PostgreSQL database and clears the auth cookie. Pass ?all=true to revoke all active sessions for this account.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"all","in":"query","required":false,"schema":{"type":"boolean","default":false},"description":"If true, revokes all active database sessions across all devices for this user."}],"responses":{"200":{"description":"Logged out and session permanently deleted from database","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Logged out successfully. Session permanently invalidated."}}}}}}},"operationId":"post_api_v1_auth_logout"}},"/api/v1/admin/users":{"get":{"tags":["Super Admin Settings"],"summary":"List platform users (ADMIN or SUPER_ADMIN)","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"search","in":"query","schema":{"type":"string","maxLength":100}}],"responses":{"200":{"description":"Paginated user summaries with distinct institution counts across staff memberships and student enrollments","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Admin role required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_admin_users","description":"List platform users (ADMIN or SUPER_ADMIN)"}},"/api/v1/portal/billing":{"get":{"tags":["Subscription Packages"],"summary":"Institution billing status and payment quotes","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription and active snapshot details, quota usage, available plan details, USD and converted BDT prices, enabled gateways","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Active institution manager required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_portal_billing","description":"Institution billing status and payment quotes"},"post":{"tags":["Subscription Packages"],"summary":"Legacy direct plan activation disabled","responses":{"409":{"description":"Checkout required before plan activation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_portal_billing","description":"Legacy direct plan activation disabled"}},"/api/v1/portal/billing/checkout":{"post":{"tags":["Subscription Packages"],"summary":"Create bKash checkout for a paid plan","description":"Active institution OWNER only. Server snapshots USD price, admin approved USD/BDT conversion rate, BDT amount and validity. The response URL is a gateway checkout; returning from it does not itself activate a subscription.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["institutionId","planId","provider"],"additionalProperties":false,"properties":{"institutionId":{"type":"string"},"planId":{"type":"string"},"provider":{"type":"string","enum":["BKASH"]}}}}}},"responses":{"200":{"description":"Gateway URL, attempt ID and quoted currency amount","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Active institution owner required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Gateway or conversion rate unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Upstream provider or gateway failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_portal_billing_checkout"}},"/api/v1/portal/billing/bkash/callback":{"get":{"tags":["Subscription Packages"],"summary":"bKash browser return; server executes and queries payment before grant","description":"The gateway redirects the browser here. The server reconciles payment before redirecting to the billing page; a success query value alone never grants a subscription.","security":[],"parameters":[{"name":"paymentID","in":"query","required":false,"schema":{"type":"string","maxLength":100}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"307":{"description":"Redirect to billing status","headers":{"Location":{"description":"Billing page with attempt or checkout status","schema":{"type":"string","format":"uri"}}}},"429":{"description":"Callback rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_portal_billing_bkash_callback"}},"/api/v1/portal/billing/manual":{"post":{"tags":["Subscription Packages"],"summary":"Verify a Send Money transaction and activate a paid package","description":"Active institution OWNER only. Amount and validity are calculated server-side. The transaction ID is unique and the subscription grant is idempotent.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["institutionId","planId","txnId"],"additionalProperties":false,"properties":{"institutionId":{"type":"string"},"planId":{"type":"string"},"txnId":{"type":"string"}}}}}},"responses":{"200":{"description":"Transaction verified and package activated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"409":{"description":"Transaction already submitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_portal_billing_manual"}},"/api/v1/portal/billing/binance":{"post":{"tags":["Subscription Packages"],"summary":"Start or verify a personal Binance Pay transfer","description":"Active institution OWNER or ADMIN. Start snapshots the USD price as the same numeric amount in USDT and returns a Receive QR URL. Verify compares the submitted transaction ID against recent incoming Binance Pay history, the configured recipient, asset, amount and unique-use record before atomically granting the subscription. History is cached briefly; a missing payment may be retried without paying again.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["action","institutionId","planId"],"additionalProperties":false,"properties":{"action":{"type":"string","enum":["start"]},"institutionId":{"type":"string"},"planId":{"type":"string"}}},{"type":"object","required":["action","attemptId","transactionId"],"additionalProperties":false,"properties":{"action":{"type":"string","enum":["verify"]},"attemptId":{"type":"string"},"transactionId":{"type":"string"}}}]}}}},"responses":{"200":{"description":"Payment attempt started or transaction verified","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Payment not found yet","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Payment method disabled, expired, or transaction already used","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Verification temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_portal_billing_binance"}},"/api/v1/admin/institutions/grant-subscription":{"post":{"tags":["Super Admin Settings"],"summary":"Manually grant institution subscription","description":"SUPER_ADMIN or ADMIN. Same active plan preserves its remaining days by default; extendExisting=false restarts validity today. Different or expired plans start today. Writes an audit log with reason.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["institutionId","planId","durationDays","reason"],"additionalProperties":false,"properties":{"institutionId":{"type":"string"},"planId":{"type":"string"},"durationDays":{"type":"integer","minimum":1,"maximum":3650},"extendExisting":{"type":"boolean","default":true},"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Subscription and entitlement snapshot updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Platform administrator required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_admin_institutions_grant_subscription"}},"/api/v1/admin/users/{id}/ban":{"patch":{"tags":["Super Admin Settings"],"summary":"Ban or restore a user","description":"Administrators can manage USER accounts; SUPER_ADMIN can also manage ADMIN accounts. Banning revokes active sessions.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["banned"],"properties":{"banned":{"type":"boolean"},"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"User access updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Insufficient privileges","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"patch_api_v1_admin_users_id_ban"}},"/api/v1/admin/institutions/{id}/ban":{"patch":{"tags":["Super Admin Settings"],"summary":"Ban or restore an institution","description":"A banned institution cannot use portal or student features until restored.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["banned"],"properties":{"banned":{"type":"boolean"},"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Institution access updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Platform administrator required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"patch_api_v1_admin_institutions_id_ban"}},"/api/v1/auth/google":{"get":{"tags":["Auth"],"summary":"Initiate Google OAuth 2.0 Web sign-in flow","description":"Redirects the browser to Google's official OAuth consent screen using server-configured GOOGLE_CLIENT_ID and scopes. A short-lived state cookie protects the redirect callback.","responses":{"302":{"description":"Redirect to accounts.google.com"},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_auth_google"},"post":{"tags":["Auth"],"summary":"Authenticate via Google ID Token or Access Token (Mobile & GIS)","description":"Android: configure Google Sign-In with the server web OAuth client ID as the ID-token audience. Send the resulting ID token here in JSON. If 2FA is off, the response includes a Trivia World JWT. If 2FA is on, the response includes requires2FA and a five-minute challengeToken instead of a JWT; call POST /api/v1/auth/2fa/complete with that token, a TOTP or recovery code, and device metadata. Do not use the browser redirect endpoint for Android.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"idToken":{"type":"string","description":"Google ID token whose audience is GOOGLE_CLIENT_ID (the server web client ID)"},"accessToken":{"type":"string","description":"Google OAuth access token"},"deviceId":{"type":"string","description":"Stable app-scoped Android device ID for per-student single active device enforcement"},"deviceName":{"type":"string","description":"Human-readable device model (e.g. Pixel 8)"}}}}}},"responses":{"200":{"description":"Google authentication successful with JWT, or requires2FA with a short-lived challengeToken and no JWT","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Invalid Google token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Device cooldown locked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Google account mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded; honor Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service unavailable or not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_auth_google"}},"/api/v1/auth/google/callback":{"get":{"tags":["Auth"],"summary":"Google OAuth 2.0 redirect callback","description":"Validates the OAuth state cookie and exchanges the authorization code from Google. Accounts with 2FA enabled receive only a short-lived challenge cookie and are redirected to /login?twoFactor=google; a full session is created after POST /api/v1/auth/2fa/complete succeeds.","parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string"}},{"name":"error","in":"query","required":false,"schema":{"type":"string"}},{"name":"state","in":"query","required":false,"schema":{"type":"string"},"description":"OAuth CSRF state returned by Google and matched against a short-lived cookie"}],"responses":{"302":{"description":"Redirect to 2FA verification or the appropriate dashboard"}},"operationId":"get_api_v1_auth_google_callback"}},"/api/v1/cron/cleanup-sessions":{"get":{"tags":["Cron Jobs"],"summary":"Purge expired session tokens and sign-in challenges from PostgreSQL","description":"Automated cron endpoint that permanently removes expired sessions and two-factor sign-in challenges. Requires CRON_SECRET via Bearer token, x-cron-secret header, or ?key= query parameter.","security":[{"CronSecret":[]}],"parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"type":"string"},"description":"Secret authorization key defined in CRON_SECRET"},{"name":"key","in":"query","required":false,"schema":{"type":"string"},"description":"Secret authorization key query parameter fallback"}],"responses":{"200":{"description":"Purged expired sessions report","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Cleaned up 12 expired session(s) from the database."},"deletedCount":{"type":"integer","example":12},"timestamp":{"type":"string","example":"2026-10-12T17:30:00.000Z"}}}}}},"401":{"description":"Unauthorized: Invalid or missing cron secret authorization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_cron_cleanup_sessions"},"post":{"tags":["Cron Jobs"],"summary":"Purge expired session tokens from PostgreSQL (POST webhook)","description":"Automated cron endpoint that permanently removes all expired session records from the database. Requires CRON_SECRET.","security":[{"CronSecret":[]}],"parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"type":"string"},"description":"Secret authorization key defined in CRON_SECRET"},{"name":"key","in":"query","required":false,"schema":{"type":"string"},"description":"Secret authorization key query parameter fallback"}],"responses":{"200":{"description":"Purged expired sessions report","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Cleaned up 12 expired session(s) from the database."},"deletedCount":{"type":"integer","example":12},"timestamp":{"type":"string","example":"2026-10-12T17:30:00.000Z"}}}}}},"401":{"description":"Unauthorized: Invalid or missing cron secret authorization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_cron_cleanup_sessions"}},"/api/v1/student/bootstrap":{"get":{"tags":["Student Bootstrap & Profile"],"summary":"Initialize student mobile app or web session","description":"Creates a student profile for a newly signed-in account if needed, then returns active institution branding, bound device status, and ad policy. General practice remains available without institution enrollment.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Bootstrap initialization payload","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"student":{"$ref":"#/components/schemas/StudentProfile"},"activeInstitution":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"name":{"type":"string"},"code":{"type":"string"},"logoUrl":{"type":"string","nullable":true},"primaryColor":{"type":"string"},"accentColor":{"type":"string"}}},"device":{"$ref":"#/components/schemas/StudentDevice"},"entitlements":{"type":"object","properties":{"showAds":{"type":"boolean"},"publicShowAds":{"type":"boolean","description":"Eligibility for public app placements; consent and ad availability are checked by the client."},"institutionAds":{"type":"object","additionalProperties":{"type":"boolean"},"description":"Current ad eligibility keyed by active enrolled institution ID. Unknown IDs must not be treated as eligible."},"canPractice":{"type":"boolean"},"canTakeExams":{"type":"boolean"}}}}}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_student_bootstrap"}},"/api/v1/student/devices":{"get":{"tags":["Student Devices"],"summary":"Get student's active bound device and cooldown status","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Registered device details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_student_devices","description":"Get student's active bound device and cooldown status"},"post":{"tags":["Student Devices"],"summary":"Bind or verify current student device","description":"Enforces single registered active device per student account with 7-day cooldown policy.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["deviceId","deviceName"],"properties":{"deviceId":{"type":"string","description":"Stable app-scoped Android device ID; reused after reinstall with the same signing key","example":"android-v1:9e31..."},"deviceName":{"type":"string","example":"Samsung Galaxy S24"},"appVersion":{"type":"string","example":"1.0.0"}}}}}},"responses":{"200":{"description":"Device bound or verified","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Blocked by single device policy with remaining cooldown hours","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_student_devices"},"delete":{"tags":["Student Devices"],"summary":"Revoke active device registration (after cooldown)","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Device registration reset","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Cannot reset yet due to active cooldown","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"delete_api_v1_student_devices","description":"Revoke active device registration (after cooldown)"}},"/api/v1/student/enroll":{"post":{"tags":["Student Institutions"],"summary":"Request institution enrollment by code","description":"Creates a PENDING enrollment. rollNumber is optional, trimmed, limited to 50 characters and cannot contain control characters. It belongs to this institution enrollment. Institution staff must approve before access or seat locking begins.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string"},"rollNumber":{"type":"string","maxLength":50},"targetExam":{"type":"string"}}}}}},"responses":{"200":{"description":"Pending request or existing active enrollment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Enrollment suspended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Code not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_student_enroll"}},"/api/v1/portal/students":{"get":{"tags":["Student Institutions"],"summary":"List institution students and pending requests, including UID, roll number and batch assignments","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"search","in":"query","description":"Empty or at least 5 characters; matches name or email","schema":{"type":"string","maxLength":100}},{"name":"status","in":"query","schema":{"type":"string","enum":["ALL","ACTIVE","PENDING","REJECTED","SUSPENDED"]}},{"name":"batchId","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated institution enrollments with totalStudents and pagination","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid search or filters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_portal_students","description":"List institution students and pending requests, including UID, roll number and batch assignments"},"post":{"tags":["Student Institutions"],"summary":"Approve, reject, or remove an enrollment","description":"Approval changes PENDING to ACTIVE and applies the configured seat lock. Removal is denied during the lock.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enrollmentId","action"],"properties":{"enrollmentId":{"type":"string"},"action":{"type":"string","enum":["APPROVE","REJECT","REMOVE"]}}}}}},"responses":{"200":{"description":"Enrollment updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden or seat locked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Invalid status transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_portal_students"},"patch":{"tags":["Student Institutions"],"summary":"Set, change or clear a student's institution roll number","description":"Active OWNER, ADMIN or TEACHER can update the roll number on an enrollment at any time. Values are trimmed, limited to 50 characters and cannot contain control characters.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enrollmentId","rollNumber"],"properties":{"enrollmentId":{"type":"string"},"rollNumber":{"type":"string","maxLength":50,"nullable":true,"description":"No line breaks or control characters. Null or empty clears the roll."}}}}}},"responses":{"200":{"description":"Enrollment roll number updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"patch_api_v1_portal_students"}},"/api/v1/portal/questions/print/manual":{"get":{"tags":["Portal Questions"],"summary":"Search all institution questions for manual paper selection","description":"Returns 30 questions per page. Search and subject, topic, and difficulty filters are applied server-side; active institution membership is required.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}},{"name":"search","in":"query","schema":{"type":"string"}},{"name":"subjectId","in":"query","schema":{"type":"string"}},{"name":"topicId","in":"query","schema":{"type":"string"}},{"name":"difficulty","in":"query","schema":{"type":"string","enum":["EASY","MEDIUM","HARD"]}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Matching question summaries with imageUrl for attached images and pagination","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_portal_questions_print_manual"},"post":{"tags":["Portal Questions"],"summary":"Confirm ordered manual question selection","description":"Validates that all selected questions are active and belong to the institution, then returns full printable questions in the requested order. Empty selection clears the paper. Maximum 500 questions.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["institutionId","questionIds"],"properties":{"institutionId":{"type":"string"},"questionIds":{"type":"array","maxItems":500,"uniqueItems":true,"items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Printable questions in selected order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Selected question unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_portal_questions_print_manual"}},"/api/v1/portal/questions/print":{"get":{"tags":["Portal Questions"],"summary":"Load the latest printable question pool and answer key for institution staff","description":"Returns up to 500 active questions with newly shuffled option positions and matching answer flags, including older questions. The printed answer key starts on a separate A4 page. When enabled in admin settings and a Public base URL is configured, printQr is a QR image encoding the optional platform name, message, and website as multiline text. Only the QR image is printed.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Printable questions, total count, and optional printQr image data URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_portal_questions_print"}},"/api/v1/portal/batches/assign":{"post":{"tags":["Student Institutions"],"summary":"Assign or unassign an approved student to a batch","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["batchId","enrollmentId","action"],"properties":{"batchId":{"type":"string"},"enrollmentId":{"type":"string"},"action":{"type":"string","enum":["ASSIGN","UNASSIGN"]}}}}}},"responses":{"200":{"description":"Batch assignment updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Student is not approved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_portal_batches_assign","description":"Assign or unassign an approved student to a batch"}},"/api/v1/portal/batches/{id}/students":{"get":{"tags":["Student Institutions"],"summary":"List batch candidates or current members","description":"Active institution owner, admin, or teacher. Use mode=available with at least 5 name or email characters to search approved candidates; mode=assigned lists current batch members and allows browsing without search.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"search","in":"query","schema":{"type":"string","minLength":5,"maxLength":100}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"mode","in":"query","schema":{"type":"string","enum":["available","assigned"],"default":"available"}}],"responses":{"200":{"description":"Paginated candidates or batch members","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_portal_batches_id_students"},"post":{"tags":["Student Institutions"],"summary":"Add approved students to a batch in bulk","description":"Validates active institution enrollment and existing assignments for every selected student. Returns added, alreadyAssigned, and ineligible counts.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enrollmentIds"],"additionalProperties":false,"properties":{"enrollmentIds":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Per-category assignment summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid selection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_portal_batches_id_students"},"delete":{"tags":["Student Institutions"],"summary":"Remove selected students from a batch","description":"Active institution owner, admin, or teacher. Removes only batch assignments; institution enrollment and other batches remain intact. Returns removed and notAssigned counts.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enrollmentIds"],"additionalProperties":false,"properties":{"enrollmentIds":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Batch removal summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid selection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"delete_api_v1_portal_batches_id_students"}},"/api/v1/portal/batches/{id}/students/export":{"get":{"tags":["Student Institutions"],"summary":"Export a batch's assigned students as CSV","description":"Active institution owner, admin, or teacher. Streams all assigned students with contact, roll, status, and enrollment dates. CSV cells are escaped for spreadsheet safety.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"UTF-8 CSV attachment with student details","content":{"text/csv":{"schema":{"type":"string"}}}},"400":{"description":"Invalid batch ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Institution staff access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Batch not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Export rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_portal_batches_id_students_export"}},"/api/v1/student/institutions":{"get":{"tags":["Student Institutions"],"summary":"List all institutions enrolled or applied to","description":"Returns each enrollment status, assigned batches and published exam count for the authenticated student. A new account receives an empty list. Only active enrollments grant exam access.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Institution enrollments, statuses, batches and active institution ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_student_institutions"},"post":{"tags":["Student Institutions"],"summary":"Join new institution by public joining code","description":"Submits admission request using joining code (e.g. 'APEX123').","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","example":"APEX123"}}}}}},"responses":{"200":{"description":"Admission request submitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Invalid institution code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_student_institutions"},"patch":{"tags":["Student Institutions"],"summary":"Switch student's active institution","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["institutionId"],"properties":{"institutionId":{"type":"string"}}}}}},"responses":{"200":{"description":"Active institution switched","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"patch_api_v1_student_institutions","description":"Switch student's active institution"}},"/api/v1/student/practice":{"get":{"tags":["Student Practice"],"summary":"Get assigned subjects and mock-test permission flags","description":"Subjects come from active batch enrollments. Each subject includes allowPracticeMode; disabled subjects remain visible for UI explanation but cannot generate a test.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Subjects and available question counts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_student_practice"},"post":{"tags":["Student Practice"],"summary":"Generate randomized practice questions session","description":"Requires an active batch assignment and institution allowPracticeMode=true. Returns a random set of active questions with shuffled option positions, matching correctOptionKey and instant explanations. count must be an integer from 5 to 50. Disabled institutions return 403.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"subjectId":{"type":"string","example":"ALL"},"difficulty":{"type":"string","enum":["ALL","EASY","MEDIUM","HARD"]},"count":{"type":"integer","minimum":5,"maximum":50,"default":10}}}}}},"responses":{"200":{"description":"Practice question set with instant explanations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid filters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Sign in required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Unassigned subject or mock tests disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No matching questions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_student_practice"}},"/api/v1/student/exams":{"get":{"tags":["Student Exams"],"summary":"List exams assigned to the authenticated student","description":"Returns only published exams with at least one assigned batch matching the student's active batch enrollment, newest creation first with createdAt. App-only exams remain visible on web for countdown and show appOnly=true, canParticipate=false and appDownloadUrl; the web cannot start or continue them. Filters by institutionId, institutionCode and state. Use startAt and serverTime for an upcoming countdown; the server still checks the schedule at start. Client channel: send X-Trivia-Client: android and X-Trivia-Device-Id with a Bearer token from a registered device for app-only exams. The marker is not cryptographic app attestation. Hidden scores are omitted.","parameters":[{"name":"X-Trivia-Client","in":"header","description":"Use android for app requests; omit or use web for dashboard requests. This marker alone is not proof of app identity.","schema":{"type":"string","enum":["android","web"]}},{"name":"X-Trivia-Device-Id","in":"header","description":"Registered active student device ID, required with a Bearer token for app-only exams.","schema":{"type":"string"}},{"name":"institutionId","in":"query","schema":{"type":"string"}},{"name":"institutionCode","in":"query","schema":{"type":"string"}},{"name":"state","in":"query","schema":{"type":"string","enum":["UPCOMING","OPEN","IN_PROGRESS","SUBMITTED","CLOSED"]}}],"security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Exam schedules, attempt records, serverTime and emptyReason (NO_ACTIVE_ENROLLMENT, NO_PUBLISHED_EXAMS, NOT_ASSIGNED or NO_MATCHING_STATE)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_student_exams"}},"/api/v1/student/exams/{id}/start":{"post":{"tags":["Student Exams"],"summary":"Start or resume an exam attempt","description":"Call only after displaying the exam briefing and receiving explicit student confirmation. Checks active enrollment, a nonempty assigned batch intersection, start/end window, app-only client access and attempt limit. Future exams return 403 with startAt. App-only requests require Bearer authentication, X-Trivia-Client: android and X-Trivia-Device-Id matching an active registered device. A header/device identifier alone cannot prove the caller is the genuine Android app; cryptographic app attestation is a separate integration. Concurrent starts are serialized. Creates immutable question snapshots and returns server deadlines without answer keys. Resuming never resets the timer.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Trivia-Client","in":"header","description":"Use android for app requests; omit or use web for dashboard requests. This marker alone is not proof of app identity.","schema":{"type":"string","enum":["android","web"]}},{"name":"X-Trivia-Device-Id","in":"header","description":"Registered active student device ID, required with a Bearer token for app-only exams.","schema":{"type":"string"}}],"responses":{"200":{"description":"Active attempt ID, timer mode, authoritative deadlines and questions without answer keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Max attempts reached or outside exam schedule","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request conflicts with the current resource state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_student_exams_id_start"}},"/api/v1/student/exams/{id}/save-answer":{"post":{"tags":["Student Exams"],"summary":"Real-time answer selection recording","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["attemptId","questionId","selectedOptionKey"],"properties":{"attemptId":{"type":"string"},"questionId":{"type":"string"},"selectedOptionKey":{"type":"string","nullable":true,"enum":["a","b","c","d"]}}}}}},"responses":{"200":{"description":"Answer recorded in snapshot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request conflicts with the current resource state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_student_exams_id_save_answer","description":"Real-time answer selection recording"}},"/api/v1/student/exams/{id}/warning":{"post":{"tags":["Student Exams"],"summary":"Report tab-switch or window blur violation","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["attemptId"],"properties":{"attemptId":{"type":"string"},"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Violation recorded; triggers auto-submit if threshold exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_student_exams_id_warning","description":"Report tab-switch or window blur violation"}},"/api/v1/student/exams/{id}/submit":{"post":{"tags":["Student Exams"],"summary":"Final exam submission and scoring","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["attemptId"],"properties":{"attemptId":{"type":"string"}}}}}},"responses":{"200":{"description":"Submission confirmation; server-calculated score only if the exam's visibility and release policy permit it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request conflicts with the current resource state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_student_exams_id_submit","description":"Final exam submission and scoring"}},"/api/v1/student/exams/{id}/results":{"get":{"tags":["Student Exams"],"summary":"Get student's exam score, merit rank, and answer review","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Submission confirmation if results are hidden; score only or full answer review with imageUrl when permitted by the exam's release and visibility settings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_student_exams_id_results","description":"Get student's exam score, merit rank, and answer review"}},"/api/v1/portal/questions":{"get":{"tags":["Portal Questions"],"summary":"Browse institution question bank","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":15}},{"name":"search","in":"query","description":"Case-insensitive question text or subject name search.","schema":{"type":"string","maxLength":100}},{"name":"subjectId","in":"query","schema":{"type":"string"}},{"name":"difficulty","in":"query","schema":{"type":"string","enum":["EASY","MEDIUM","HARD"]}}],"responses":{"200":{"description":"Paginated questions with resolved imageUrl for attached diagrams, deletion policy, and remaining quota limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_portal_questions","description":"Browse institution question bank"},"post":{"tags":["Portal Questions"],"summary":"Create single MCQ question","description":"Requires four options and exactly one correct answer. The server shuffles options on save and reassigns position keys a–d while preserving the correct answer text.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["institutionId","subjectId","questionText","options"],"properties":{"institutionId":{"type":"string"},"subjectId":{"type":"string"},"topicId":{"type":"string","nullable":true},"questionText":{"type":"string"},"explanation":{"type":"string","nullable":true},"difficulty":{"type":"string","enum":["EASY","MEDIUM","HARD"]},"imageKey":{"type":"string","nullable":true},"options":{"type":"array","minItems":4,"maxItems":4,"items":{"type":"object","required":["key","text","isCorrect"],"properties":{"key":{"oneOf":[{"type":"string"},{"type":"number"}]},"text":{"type":"string"},"isCorrect":{"type":"boolean"}}}}}}}}},"responses":{"200":{"description":"Question created with shuffled options","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request conflicts with the current resource state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_portal_questions"}},"/api/v1/portal/questions/export":{"get":{"tags":["Portal Questions"],"summary":"Export active questions in one subject","description":"Only an active institution OWNER or ADMIN can export questions in the requested subject. Returns a CSV or JSON attachment.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}},{"name":"subjectId","in":"query","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","required":true,"schema":{"type":"string","enum":["csv","json"]}}],"responses":{"200":{"description":"Question export","content":{"text/csv":{"schema":{"type":"string"}},"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Active owner or admin required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_portal_questions_export"}},"/api/v1/portal/questions/bulk":{"post":{"tags":["Portal Questions"],"summary":"Bulk import questions from CSV/JSON parser","description":"Enforces quota integrity and atomicity via database transactions. Each imported question must have four options and one correct answer; options are shuffled on save.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["institutionId","subjectId","questions"],"properties":{"institutionId":{"type":"string"},"subjectId":{"type":"string"},"topicId":{"type":"string"},"questions":{"type":"array","minItems":1,"items":{"type":"object","required":["questionText","options"],"properties":{"questionText":{"type":"string"},"explanation":{"type":"string"},"difficulty":{"type":"string","enum":["EASY","MEDIUM","HARD"]},"options":{"type":"array","minItems":4,"maxItems":4,"items":{"type":"object","required":["key","text","isCorrect"],"properties":{"key":{"oneOf":[{"type":"string"},{"type":"number"}]},"text":{"type":"string"},"isCorrect":{"type":"boolean"}}}}}}}}}}}},"responses":{"200":{"description":"Bulk import count","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Request conflicts with the current resource state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_portal_questions_bulk"}},"/api/v1/portal/questions/upload-image":{"post":{"tags":["Portal Questions"],"summary":"Upload MCQ image via active cloud storage driver","description":"Requires active institution membership and a paid plan with imageUploadsEnabled. Accepts client-compressed WebP within configured size and dimension limits, saves to the active storage provider, and returns a relative storage key.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["institutionId","file"],"properties":{"institutionId":{"type":"string"},"file":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Relative imageKey and resolved public imageUrl","content":{"application/json":{"schema":{"type":"object","required":["success","imageKey","imageUrl"],"properties":{"success":{"type":"boolean"},"imageKey":{"type":"string"},"imageUrl":{"type":"string"}}}}}},"400":{"description":"Invalid image format, size or dimensions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Membership or paid image-upload entitlement required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_portal_questions_upload_image"},"delete":{"tags":["Portal Questions"],"summary":"Remove an unsaved question image","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["institutionId","imageKey"],"properties":{"institutionId":{"type":"string"},"imageKey":{"type":"string"}}}}}},"responses":{"200":{"description":"Unsaved upload removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Image is attached to a saved question","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"delete_api_v1_portal_questions_upload_image","description":"Remove an unsaved question image"}},"/api/v1/settings/public":{"get":{"tags":["Public Settings"],"summary":"Get public site settings and dynamic brand theme colors","description":"Includes settings.public_show_ads, the current public Android ad eligibility flag. Mobile clients must also obtain ad consent before loading a placement.","responses":{"200":{"description":"Site metadata, title, and theme colors (primary, accent)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_settings_public"}},"/api/v1/admin/settings":{"get":{"tags":["Super Admin Settings"],"summary":"Get platform system settings","description":"ADMIN and SUPER_ADMIN can read masked settings. SUPER_ADMIN additionally receives saved Supabase, R2 and Neon bucket names, plus decrypted storageKeys and paymentKeys for the settings form. Cache-Control is private, no-store.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Settings and credential metadata","content":{"application/json":{"schema":{"type":"object","required":["success","settings"],"properties":{"success":{"type":"boolean"},"settings":{"type":"object","additionalProperties":{"type":"string"}},"credentials":{"type":"object","description":"SUPER_ADMIN only: nonsecret credential metadata."},"storageKeys":{"type":"object","description":"SUPER_ADMIN only: decrypted storage keys. Treat as secret and do not cache.","properties":{"supabaseKey":{"type":"string"},"r2AccessKey":{"type":"string"},"r2SecretKey":{"type":"string"},"neonAccessKey":{"type":"string"},"neonSecretKey":{"type":"string"}}},"paymentKeys":{"type":"object","description":"SUPER_ADMIN only: decrypted Binance API key and secret. Treat as secret and do not cache.","properties":{"binanceApiKey":{"type":"string"},"binanceApiSecret":{"type":"string"}}}}}}}},"403":{"description":"Platform admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_admin_settings"},"post":{"tags":["Super Admin Settings"],"summary":"Update system, storage, payment and recovery settings","description":"SUPER_ADMIN only. Accepts a strict object of string-valued settings. Configures SMTP, public site base URL, manual transaction verification, bKash, Binance, storage, appearance and print QR options. Supabase, R2 and Neon buckets are configurable. Switching to R2 requires a public HTTPS URL. A storage switch affects new uploads; existing location-aware image keys keep their original provider and bucket. Pre-migration Neon images are captured before the first storage settings change. SMTP, Binance and storage credentials are encrypted at rest; the GET operation returns storage and Binance keys only to SUPER_ADMIN.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"storage_active_driver":{"type":"string","enum":["SUPABASE","R2","NEON"]},"image_max_dimension_px":{"type":"string","pattern":"^[0-9]+$","description":"0 disables MCQ image uploads; otherwise maximum dimension in pixels."},"image_max_size_kb":{"type":"string","pattern":"^[0-9]+$","description":"0 disables MCQ image uploads; otherwise maximum size in KB."},"free_question_limit":{"type":"string","pattern":"^[0-9]+$","description":"0 allows no free question creation."},"question_delete_protection_days":{"type":"string","pattern":"^[0-9]+$","description":"0 disables the deletion lock."},"student_seat_lock_days":{"type":"string","pattern":"^[0-9]+$","description":"0 disables the lock for newly approved seats."},"device_reset_cooldown_days":{"type":"string","pattern":"^[0-9]+$","description":"0 disables the device switch cooldown."},"audit_institution_retention_days":{"type":"string","pattern":"^[0-9]+$","description":"0 disables automatic institution audit log cleanup."},"audit_system_retention_days":{"type":"string","pattern":"^[0-9]+$","description":"0 disables automatic system audit log cleanup."},"site_title":{"type":"string","minLength":1,"maxLength":120},"site_description":{"type":"string","maxLength":500},"brand_colors":{"type":"string","description":"JSON string with primaryBrand, accentAmber and accentCyan as #RRGGBB colors."},"enable_email_password_login":{"type":"string","enum":["true","false"]},"public_show_ads":{"type":"string","enum":["true","false"],"description":"Allow ads on public Android content. Independent of institution package ads."},"site_base_url":{"type":"string","description":"Empty string, HTTPS origin, or local HTTP origin. Used for password recovery links, Google OAuth callback, and print QR destination."},"mcp_tutorial_url":{"type":"string","description":"YouTube watch, short, live, embed, or youtu.be URL (or 11-character ID). Empty string removes the tutorial; only the video ID is stored."},"smtp_host":{"type":"string","maxLength":255},"smtp_port":{"type":"string","pattern":"^[0-9]+$"},"smtp_secure":{"type":"string","enum":["true","false"]},"smtp_username":{"type":"string","maxLength":255},"smtp_password":{"type":"string","writeOnly":true,"maxLength":1024},"smtp_from_email":{"type":"string","description":"Sender email, or empty string while SMTP is unconfigured."},"print_qr_enabled":{"type":"string","enum":["true","false"]},"print_qr_name":{"type":"string","maxLength":120,"description":"Optional platform name encoded inside the QR code."},"print_qr_about":{"type":"string","maxLength":240,"description":"Optional short platform message encoded inside the QR code."},"billing_usd_bdt_rate":{"type":"string","example":"122.5000"},"billing_manual_enabled":{"type":"string","enum":["true","false"]},"billing_manual_base_url":{"type":"string","description":"Verification service URL, or empty while manual payments are disabled."},"billing_manual_pkg":{"type":"string"},"billing_manual_mobile":{"type":"string","pattern":"^[0-9]{11}$"},"billing_manual_instructions":{"type":"string","description":"Plain text shown to customers."},"billing_bkash_enabled":{"type":"string","enum":["true","false"]},"bkash_app_key":{"type":"string","writeOnly":true},"bkash_app_secret":{"type":"string","writeOnly":true},"bkash_username":{"type":"string","writeOnly":true},"bkash_password":{"type":"string","writeOnly":true},"bkash_environment":{"type":"string","enum":["sandbox","production"]},"supabase_url":{"type":"string"},"supabase_service_role_key":{"type":"string","writeOnly":true},"supabase_bucket":{"type":"string","minLength":1,"maxLength":255},"r2_account_id":{"type":"string"},"r2_access_key_id":{"type":"string","writeOnly":true},"r2_secret_access_key":{"type":"string","writeOnly":true},"r2_bucket":{"type":"string","minLength":1,"maxLength":255},"r2_public_url":{"type":"string","description":"Public HTTPS base URL for objects. Required before activating R2."},"neon_endpoint":{"type":"string"},"neon_region":{"type":"string"},"neon_bucket":{"type":"string","minLength":1,"maxLength":255},"neon_access_key_id":{"type":"string","writeOnly":true},"neon_secret_access_key":{"type":"string","writeOnly":true},"neon_public_url":{"type":"string"}}}}}},"responses":{"200":{"description":"Settings saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid settings or missing encryption key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"SUPER_ADMIN access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_admin_settings"}},"/api/v1/admin/cache/purge":{"post":{"tags":["Super Admin Settings"],"summary":"Purge the server data cache","description":"SUPER_ADMIN only. Expires all tagged server data cache entries; the next read reloads data. Limited to two requests per minute and audited.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"Server data cache invalidated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"403":{"description":"Super Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Purge rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_admin_cache_purge"}},"/api/v1/admin/settings/storage-test":{"post":{"tags":["Super Admin Settings"],"summary":"Test saved object storage credentials","description":"SUPER_ADMIN only. Neon uploads and deletes a zero-byte test object. Supabase and R2 check bucket access. Saved credentials are used server-side.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["provider"],"properties":{"provider":{"type":"string","enum":["SUPABASE","R2","NEON"]}}}}}},"responses":{"200":{"description":"Storage access verified; Neon test object uploaded and removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid provider or saved configuration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Super Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Storage provider denied access or connection failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_admin_settings_storage_test"}},"/api/v1/admin/settings/storage-credentials/reveal":{"post":{"tags":["Super Admin Settings"],"summary":"Reveal saved storage keys on demand","description":"SUPER_ADMIN only. Returns the saved keys for one selected storage provider. The response is private and not cached.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["provider"],"properties":{"provider":{"type":"string","enum":["SUPABASE","R2","NEON"]}}}}}},"responses":{"200":{"description":"Saved keys for the selected provider","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid provider or saved keys unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Super Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_admin_settings_storage_credentials_reveal"}},"/api/v1/admin/settings/payment-test":{"post":{"tags":["Super Admin Settings"],"summary":"Test saved payment gateway credentials","description":"SUPER_ADMIN only. Checks the manual endpoint with HEAD or requests a bKash access token. This does not create a payment.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["provider"],"properties":{"provider":{"type":"string","enum":["MANUAL","BKASH"]}}}}}},"responses":{"200":{"description":"Payment gateway authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid provider or connection failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Super Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_admin_settings_payment_test"}},"/api/v1/plans":{"get":{"tags":["Subscription Packages"],"summary":"Get active public subscription packages","description":"Publicly accessible endpoint returning active subscription packages with features and pricing in USD ($).","responses":{"200":{"description":"List of active subscription plans","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"plans":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"priceUsd":{"type":"number","example":24},"validityDays":{"type":"integer","minimum":1,"maximum":3650},"adsEnabled":{"type":"boolean"},"bulkImportEnabled":{"type":"boolean"},"imageUploadsEnabled":{"type":"boolean"},"mcpEnabled":{"type":"boolean"},"mcpKeyLimit":{"type":"integer"},"isPopular":{"type":"boolean","example":true},"maxStudents":{"type":"integer","example":500},"maxQuestions":{"type":"integer","example":5000},"maxQuestionsPerTest":{"type":"integer"},"resultRetentionDays":{"type":"integer"},"features":{"type":"array","items":{"type":"string"}},"details":{"type":"array","description":"All displayed plan terms, including unavailable tools. Free question capacity reflects the global free limit.","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"value":{"type":"string"},"available":{"type":"boolean"}}}}}}}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_plans"}},"/api/v1/admin/plans":{"get":{"tags":["Subscription Packages"],"summary":"List all plans for platform administration","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"List of all plans with subscription counts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_admin_plans","description":"List all plans for platform administration"},"post":{"tags":["Subscription Packages"],"summary":"Create or update a subscription package","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","slug","maxStudents","maxQuestions","maxQuestionsPerTest","resultRetentionDays","adsEnabled","bulkImportEnabled","imageUploadsEnabled"],"properties":{"id":{"type":"string","description":"Omit for create, include for update"},"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"priceUsd":{"type":"number","example":24},"isPopular":{"type":"boolean","example":false},"maxStudents":{"type":"integer"},"maxQuestions":{"type":"integer","description":"Paid plan capacity. The free tier creation cap follows the global Free Question Limit setting."},"maxQuestionsPerTest":{"type":"integer"},"resultRetentionDays":{"type":"integer"},"validityDays":{"type":"integer","minimum":1,"maximum":3650,"default":30,"description":"Term in days. Renewing the same package extends its expiry; switching package starts from today."},"adsEnabled":{"type":"boolean"},"bulkImportEnabled":{"type":"boolean"},"imageUploadsEnabled":{"type":"boolean","description":"Paid packages only; free packages cannot enable image uploads."},"mcpEnabled":{"type":"boolean","description":"Paid packages only; free packages cannot enable MCP."},"mcpKeyLimit":{"type":"integer","minimum":0,"maximum":100000,"description":"Maximum simultaneously active MCP API keys per institution. At least 1 when MCP is enabled."},"isActive":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Package saved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_admin_plans","description":"Create or update a subscription package"},"delete":{"tags":["Subscription Packages"],"summary":"Permanently delete a subscription package","description":"Deletes a package by ID if there are no active institution subscriptions attached to it.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string"},"description":"ID of the plan to delete"}],"responses":{"200":{"description":"Plan deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Cannot delete plan with active subscriptions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"delete_api_v1_admin_plans"}},"/api/v1/portal/institutions":{"patch":{"tags":["Institutions & Academies"],"summary":"Correct and resubmit a rejected institution application","description":"The applicant may update their rejected application and return it to pending review.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["applicationId","name"],"properties":{"applicationId":{"type":"string"},"name":{"type":"string"},"code":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"primaryColor":{"type":"string"},"accentColor":{"type":"string"},"address":{"type":"string"},"city":{"type":"string"},"country":{"type":"string"},"contactEmail":{"type":"string"},"contactPhone":{"type":"string"},"websiteUrl":{"type":"string"},"facebookUrl":{"type":"string"},"instagramUrl":{"type":"string"},"youtubeUrl":{"type":"string"},"linkedinUrl":{"type":"string"}}}}}},"responses":{"200":{"description":"Application resubmitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid application","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Authentication required or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Another application is pending","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"patch_api_v1_portal_institutions"},"get":{"tags":["Institutions & Academies"],"summary":"Get current user's institution memberships","description":"Returns active institution memberships, quotas, and the user's pending or rejected applications with review notes.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"responses":{"200":{"description":"List of user institutions","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"institutions":{"type":"array","items":{"type":"object"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_portal_institutions"},"post":{"tags":["Institutions & Academies"],"summary":"Request a new institution / academy","description":"Creates a PENDING application for admin review. The institution, OWNER membership, and starter plan are created only after approval. A user may have one pending application at a time; approval enforces the owner's creation entitlement.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"Apex International Academy"},"code":{"type":"string","example":"APEX99","description":"3-10 uppercase alphanumeric characters. Auto-generated if omitted."},"slug":{"type":"string","example":"apex-academy","description":"URL slug. Auto-generated if omitted."},"description":{"type":"string","example":"BCS and university admission exam coaching"},"primaryColor":{"type":"string","example":"#2563EB"},"accentColor":{"type":"string","example":"#F59E0B"},"address":{"type":"string"},"city":{"type":"string"},"country":{"type":"string"},"contactEmail":{"type":"string","format":"email"},"contactPhone":{"type":"string"},"websiteUrl":{"type":"string","format":"uri"},"facebookUrl":{"type":"string","format":"uri"},"instagramUrl":{"type":"string","format":"uri"},"youtubeUrl":{"type":"string","format":"uri"},"linkedinUrl":{"type":"string","format":"uri"}}}}}},"responses":{"201":{"description":"Institution application submitted for review","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"A pending application already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_portal_institutions"}},"/api/v1/portal/subjects":{"get":{"tags":["Subjects & Categories"],"summary":"Get institution subjects","description":"Fetch all academic subjects and categories configured for an institution.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"},"description":"Target institution ID"}],"responses":{"200":{"description":"List of subjects","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_v1_portal_subjects"},"post":{"tags":["Subjects & Categories"],"summary":"Create a new subject","description":"Add a subject category to the institution's question repository.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["institutionId","name"],"properties":{"institutionId":{"type":"string"},"name":{"type":"string","example":"Physics"},"code":{"type":"string","example":"PHY-101"},"description":{"type":"string","example":"Mechanics and Optics"}}}}}},"responses":{"200":{"description":"Subject created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_v1_portal_subjects"},"delete":{"tags":["Subjects & Categories"],"summary":"Delete an empty subject","description":"Delete an academic subject if it contains 0 questions.","security":[{"BearerAuth":[]},{"CookieAuth":[]}],"parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string"}},{"name":"institutionId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Subject deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Cannot delete subject containing questions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Access denied or entitlement unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Requested resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"delete_api_v1_portal_subjects"}}}}