{"openapi":"3.0.0","info":{"title":"Pria Admin API","version":"2.0.1","description":"Pria API Documentation Praxis's developer platform is a core part of our mission to empower organizations to grow better. Our APIs are designed to enable teams of any shape or size to build robust integrations that help them customize and get the most value out of Pria. All Pria APIs are built using REST conventions and designed to have a predictable URL structure. <br/>  <br/>They use many standard HTTP features, including methods (POST, GET, PUT, DELETE) and error response codes.  <br/> <br/>All API calls are made under https://hiimpria.ai/api and all responses return standard JSON. In these docs, you'll find lists of all available endpoints for a given API, along with interactive code blocks for building requests. For walkthroughs of basic usage for these APIs, check out the API guides."},"servers":[{"url":"https://pria.praxislxp.com","description":"Pria Admin API Server"}],"paths":{"/api/test/health":{"get":{"summary":"Check status of the middleware application","description":"Returns the current health status of the service including uptime, memory usage, and dependency status","responses":{"200":{"description":"Service is healthy and operational","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"},"example":{"status":"ok","uptime":112.368138,"timestamp":1750534150795,"summary":"Event loop lag: 12ms | RSS: 280MB | heap: 158MB/170MB | mongo: connected","eventLoopLag":{"meanMs":1.2,"p50Ms":0.4,"p95Ms":6.1,"p99Ms":15,"maxMs":42},"mongo":{"state":1,"label":"connected"},"memory":{"rss":128008192,"heapTotal":72105984,"heapUsed":67442352,"external":23693540,"arrayBuffers":19882176,"limitBytes":17179869184,"rssRatio":0.0075},"dependencies":{"database":1}}}}}},"tags":["Testing"]}},"/api/test/url":{"post":{"summary":"Retrieves an institution and validates user access for a specific context","description":"Use this method to determine if your originating URL or institution ID is associated with an institution,\nand optionally validate user access status. The properties returned can be used for branding your AI application.\n\n**Rate Limiting:** 30 requests per minute per IP address.\n\n**Lookup Priority:**\n1. First attempts to find institution by matching `url` against registered LTI context IDs\n2. If not found, falls back to direct lookup by `institutionid` (publicId)\n\n**User Validation (optional):**\nWhen `email` or `userid` is provided, the endpoint also validates:\n- User account status (must be 'active')\n- User-Institution membership status (must be 'active')\n\n**Status Values:**\n- `active`: Institution/user is active and accessible\n- `inactive`: Institution or user account is inactive\n- `pending`: User membership is pending approval\n- `suspended`: User membership is temporarily suspended\n- `invalid`: No institution found matching the provided criteria\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestUrlRequest"},"examples":{"ltiLookup":{"summary":"LTI Context URL lookup","value":{"url":"https://domain.edu/course/12345"}},"institutionLookup":{"summary":"Direct institution lookup","value":{"institutionid":"bc6efd03-9d01-43e7-bd49-c4af1c54ae3a"}},"fullValidation":{"summary":"Full user validation","value":{"url":"https://domain.edu/course/12345","email":"student@domain.edu"}}}}}},"responses":{"200":{"description":"URL test completed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestUrlResponse"},"examples":{"activeInstitution":{"summary":"Active institution found","value":{"ltiassigned":true,"status":"active","name":"Acme University","ainame":"Hugo","picture":"https://storage.example.com/logo.png","publicId":"bc6efd03-9d01-43e7-bd49-c4af1c54ae3a"}},"suspendedUser":{"summary":"User membership suspended","value":{"ltiassigned":true,"status":"suspended","name":"Acme University","ainame":"Hugo","picture":"https://storage.example.com/logo.png","publicId":"bc6efd03-9d01-43e7-bd49-c4af1c54ae3a"}},"notFound":{"summary":"No institution found","value":{"ltiassigned":false,"status":"invalid"}}}}}},"400":{"description":"Bad request - invalid URL format"},"500":{"description":"Internal server error"}},"tags":["Testing"]}},"/api/auth/signup":{"post":{"summary":"Register a new user","description":"Creates a new user account with email and password authentication.\n\n**Rate Limiting:** 5 requests per minute per IP address.\n","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"}}}},"responses":{"200":{"description":"User registered and authenticated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupResponse"}}}},"400":{"description":"Bad request - Invalid email, password too short, or user already exists","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"User already exists"}}}}}}}}},"/api/auth/generateResetCode":{"post":{"summary":"Generate password reset code","description":"Sends a password reset code to the user's email address.\n\n**Rate Limiting:** 3 requests per minute per IP address.\n","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResetCodeRequest"}}}},"responses":{"200":{"description":"Reset code generated and sent successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResetCodeResponse"}}}},"400":{"description":"Bad request - Invalid email"},"404":{"description":"User not found"}}}},"/api/auth/checkResetCode":{"post":{"summary":"Validate password reset code","description":"Validates whether the provided reset code is valid for the user.\n\n**Rate Limiting:** 10 requests per minute per IP address.\n","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckResetCodeRequest"}}}},"responses":{"200":{"description":"Reset code validation result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckResetCodeResponse"}}}},"400":{"description":"Bad request - Invalid or expired reset code"}}}},"/api/auth/changePassword":{"post":{"summary":"Change user password","description":"Changes the user's password using a valid reset code.\n\n**Rate Limiting:** 10 requests per minute per IP address.\n","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}}},"responses":{"200":{"description":"Password changed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordResponse"}}}},"400":{"description":"Bad request - Invalid reset code or password too short"}}}},"/api/auth/checkActivateCode":{"post":{"summary":"Validate account activation code","description":"Validates whether the provided activation code is valid for the user.\n\n**Rate Limiting:** 10 requests per minute per IP address.\n","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckActivateCodeRequest"}}}},"responses":{"200":{"description":"Activation code validation result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckActivateCodeResponse"}}}},"400":{"description":"Bad request - Invalid or expired activation code"}}}},"/api/auth/sso/{id}":{"get":{"summary":"Initiate SSO login","description":"Initiates OAuth 2.0 SSO login for a specific provider","tags":["OAuth"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"SSO provider identifier"}],"responses":{"302":{"description":"Redirect to SSO provider"}}}},"/api/auth/sso_callback":{"post":{"summary":"SSO callback","description":"Handles the callback from OAuth 2.0 SSO authentication","tags":["OAuth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"Authorization code"},"state":{"type":"string","description":"State parameter"}}}}}},"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignInResponse"}}}}}}},"/api/auth/getInstitutionsForContextid":{"post":{"summary":"Get institutions for LTI context","description":"Retrieves institutions associated with a specific LTI context ID","tags":["Institutions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetInstitutionsForContextRequest"}}}},"responses":{"200":{"description":"Institutions retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetInstitutionsForContextResponse"}}}}}}},"/api/auth/github/webhook":{"post":{"summary":"GitHub Marketplace webhook receiver","description":"Receives `marketplace_purchase` events from GitHub when an org or user\npurchases, upgrades, downgrades, or cancels the Pria Marketplace listing.\nThe endpoint validates the request via the `X-Hub-Signature-256` HMAC header\n(computed by GitHub with `GITHUB_WEBHOOK_SECRET`), then dispatches by `action`\n(`purchased`, `changed`, `cancelled`, `pending_change`, `pending_change_cancelled`).\n\nOn a new purchase the handler auto-creates the Pria user (via `autosignup`),\ngenerates a one-time login link (`?otp=<base64>` in the URL since the link is\ndelivered by email — see `docs/index/oauth-otp.md`), and emails it to the\npurchaser.\n\n**Authentication:** none in the traditional sense — request authenticity is\nproven by the `X-Hub-Signature-256` HMAC, compared with `crypto.timingSafeEqual`.\nRequests with a missing or wrong signature are rejected with 401. Application\nerrors that occur *after* signature validation still return 200 so GitHub does\nnot retry; the failure is surfaced via internal email instead.\n","tags":["OAuth"],"parameters":[{"in":"header","name":"X-Hub-Signature-256","required":true,"schema":{"type":"string"},"description":"HMAC-SHA256 of the raw request body, prefixed with `sha256=`.","example":"sha256=a1b2c3d4..."},{"in":"header","name":"X-GitHub-Event","schema":{"type":"string"},"description":"GitHub event type. Only `marketplace_purchase` is acted on; everything else is logged and acknowledged.","example":"marketplace_purchase"},{"in":"header","name":"X-GitHub-Delivery","schema":{"type":"string"},"description":"Per-delivery UUID assigned by GitHub (for log correlation)."}],"requestBody":{"required":true,"description":"Raw GitHub Marketplace payload. The handler reads `req.body` as a Buffer and\nJSON-parses after signature verification. See GitHub's marketplace_purchase\ndocumentation for the full shape.\n","content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["purchased","changed","cancelled","pending_change","pending_change_cancelled"],"example":"purchased"},"effective_date":{"type":"string","format":"date-time"},"sender":{"type":"object","properties":{"login":{"type":"string"},"id":{"type":"integer"},"type":{"type":"string"},"email":{"type":"string","nullable":true},"avatar_url":{"type":"string"}}},"marketplace_purchase":{"type":"object","properties":{"account":{"type":"object"},"plan":{"type":"object"},"billing_cycle":{"type":"string"},"unit_count":{"type":"integer"},"on_free_trial":{"type":"boolean"},"free_trial_ends_on":{"type":"string","nullable":true},"next_billing_date":{"type":"string"}}}}}}}},"responses":{"200":{"description":"Webhook accepted. The handler always returns 200 once the signature passes\n— even if the downstream business logic threw — so GitHub does not retry.\nOn internal failures the response includes `error`.\n","content":{"application/json":{"schema":{"type":"object","properties":{"received":{"type":"boolean","example":true},"error":{"type":"string","description":"Present only when post-signature processing threw."}}}}}},"401":{"description":"Missing `X-Hub-Signature-256` header, or the signature does not match\n`HMAC_SHA256(GITHUB_WEBHOOK_SECRET, body)`.\n","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid signature"}}}}}}}}},"/api/auth/google/services/authorize":{"get":{"summary":"Start the Google Services (per-user) OAuth consent flow","description":"Begins a Google OAuth 2.0 authorization-code flow scoped to the per-user services\nthe caller requests (Gmail / Drive / Calendar / Sheets / Docs). Generates a CSRF\nstate, stores it in `req.session.oauth_state` along with the caller's `userId`\nand institution context, then 302-redirects to Google's consent screen with\n`access_type=offline` and `prompt=consent` to ensure a refresh token is issued.\n\n**Storage routing (Option B):**\n- If the JWT has an institution attached, the resulting token is stored on\n  `UserInstitution.googleLoginToken` (institution-specific).\n- If there is no institution, it goes on `User.googleLoginToken` (personal).\n\n**Origin handling:** the `origin` query param (`profile` default, `chat` for the\nin-chat consent prompt) is preserved in the session so the post-callback redirect\nreturns the user to the right place.\n","tags":["OAuth"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"scopes","required":true,"schema":{"type":"string"},"description":"Comma-separated list of Google service names to request. Each name is\nexpanded via `GoogleServicesConfig.buildScopes`. Common values:\n`gmail`, `drive`, `calendar`, `sheets`, `docs`.\n","example":"gmail,calendar"},{"in":"query","name":"origin","schema":{"type":"string","enum":["profile","chat"],"default":"profile"},"description":"Where the user was when they triggered the consent. Controls the post-callback\nredirect — `chat` returns to the chat page, `profile` returns to the profile page.\n"}],"responses":{"302":{"description":"Redirect to Google's OAuth consent screen."},"400":{"description":"`scopes` is missing or empty.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"No services specified"}}}}}},"401":{"description":"Missing or invalid JWT (verifyToken)."}}}},"/api/auth/google/services/callback":{"get":{"summary":"Google Services OAuth callback","description":"Google redirects here after the user accepts (or rejects) the consent screen.\nValidates the CSRF `state` against `req.session.oauth_state`, exchanges the\nauthorization `code` for tokens, then stores them on `UserInstitution.googleLoginToken`\nor `User.googleLoginToken` depending on the institution context captured at\nauthorize time.\n\nOn success the user is redirected to `${PRIA_URL}/my-profile/me?oauth=google&status=success`\n(profile origin) or `${PRIA_URL}/pria/personal/qanda` (chat origin). On failure the user\nis redirected to `${PRIA_URL}/oauth/success?error=<code>` where the SPA renders the\nerror. This callback is **not** a JWT-protected endpoint — auth is proven by the\nsession-bound CSRF state, identical to the public OAuth login callbacks.\n","tags":["OAuth"],"parameters":[{"in":"query","name":"code","schema":{"type":"string"},"description":"Authorization code returned by Google. Required on success."},{"in":"query","name":"state","schema":{"type":"string"},"description":"CSRF state to compare against `req.session.oauth_state.value`."},{"in":"query","name":"error","schema":{"type":"string"},"description":"Set by Google when the user denies consent or the request is malformed."}],"responses":{"302":{"description":"Always a redirect. On success → profile or chat page (per the captured `origin`).\nOn any failure (missing code, state mismatch, token exchange error) →\n`${PRIA_URL}/oauth/success?error=<code>`.\n"}}}},"/api/auth/google/services/validate":{"get":{"summary":"Validate the caller's stored Google Services token","description":"Checks whether the caller's stored Google access token is still accepted by Google\nby making a probing call to the userinfo endpoint. Resolves which storage location\nto read from in the same priority order as the runtime authenticator: institution\ntoken first (when the JWT has an institution), personal token second.\n\nIf Google rejects the token (revoked externally, expired refresh chain, etc.),\nthe handler clears it from the database and returns `valid: false, cleared: true`.\nThis is a side-effecting probe — calling it can wipe a stale token.\n","tags":["OAuth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Validation completed. Inspect `valid` for the outcome.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleOAuthValidateResponse"}}}},"401":{"description":"Missing or invalid JWT (verifyToken)."},"404":{"description":"The caller's User record could not be loaded.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"User not found"}}}}}},"500":{"description":"Unexpected error during validation.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Validation request failed"}}}}}}}}},"/api/auth/google/services/revoke":{"delete":{"summary":"Revoke the caller's stored Google Services token","description":"Clears the caller's stored Google OAuth token from the database — institution-specific\n(`UserInstitution.googleLoginToken`) when the JWT has institution context, otherwise\npersonal (`User.googleLoginToken`). Best-effort: also calls Google's\n`https://oauth2.googleapis.com/revoke` to invalidate the token at Google's end. If\nthe upstream call fails (e.g. token already expired), the local database is still\ncleared and the endpoint still returns success.\n","tags":["OAuth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Token cleared locally (Google upstream revoke is best-effort).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleOAuthRevokeResponse"}}}},"401":{"description":"Missing or invalid JWT (verifyToken)."},"404":{"description":"The caller's User record could not be loaded.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"User not found"}}}}}},"500":{"description":"Unexpected error during revoke.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Failed to revoke Google services access"}}}}}}}}},"/api/auth/google/institution/authorize":{"get":{"summary":"Start the Google OAuth flow for an institution-level token","description":"Begins an institution-scoped Google OAuth 2.0 authorization-code flow. The\nresulting token is later stored on `Institution.cloudServices.google.googleLoginToken`\nand is used as the **shared** institution credential for downstream Google API\ncalls performed on behalf of any member of that institution.\n\nThe caller MUST hold `institutions.edit` on the target institution (checked via\n`checkRAPForUI`). The captured session state records the connecting user's `_id`\n(`connectedByUserId`) so the callback can attribute the connection.\n","tags":["OAuth"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"institutionId","required":true,"schema":{"type":"string"},"description":"ObjectId of the institution to attach the token to.","example":"6631915765bb0a94cfd6ca99"},{"in":"query","name":"scopes","required":true,"schema":{"type":"string"},"description":"Comma-separated Google service names. Resolved via\n`GoogleServicesConfig.buildScopes`.\n","example":"gmail,drive,calendar"}],"responses":{"302":{"description":"Redirect to Google's OAuth consent screen. On error during initiation\n(configuration / lookup failure) the user is redirected to\n`${PRIA_URL}/oauth/success?error=auth_initiation_failed` instead.\n"},"400":{"description":"`institutionId` or `scopes` missing.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Institution ID required"}}}}}},"401":{"description":"Missing or invalid JWT (verifyToken)."},"403":{"description":"Caller does not hold `institutions.edit` on the target institution.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Not authorized to configure this institution"}}}}}},"404":{"description":"Institution not found.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Institution not found"}}}}}}}}},"/api/auth/google/institution/{institutionId}/validate":{"get":{"summary":"Validate an institution's stored Google token","description":"Probes Google's userinfo endpoint with the institution's stored\n`cloudServices.google.googleLoginToken` to check whether it is still accepted.\nIf Google rejects the token, the entire `cloudServices.google` subtree is\ncleared (`$unset`) and the response reports `valid: false, cleared: true`.\nThe caller must hold `institutions.edit` on the target institution.\n","tags":["OAuth"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"institutionId","required":true,"schema":{"type":"string"},"description":"ObjectId of the institution whose token to validate."}],"responses":{"200":{"description":"Validation completed. Inspect `valid` for the outcome.","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean","example":true},"cleared":{"type":"boolean","description":"True when an invalid token was just unset."},"message":{"type":"string","description":"Set when no Google token is configured for the institution.","example":"No Google tokens configured"},"error":{"type":"string","description":"Set on validation failure (passes through Google's error)."}}}}}},"401":{"description":"Missing or invalid JWT (verifyToken)."},"403":{"description":"Caller does not hold `institutions.edit`.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Not authorized"}}}}}},"404":{"description":"Institution not found.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Institution not found"}}}}}},"500":{"description":"Unexpected error during validation.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Validation request failed"}}}}}}}}},"/api/auth/google/institution/{institutionId}/revoke":{"delete":{"summary":"Revoke an institution's stored Google token","description":"Clears `cloudServices.google` from the target institution (`$unset`), and\nbest-effort revokes the access token at Google. The caller must hold\n`institutions.edit`. As with the per-user revoke, an upstream revoke failure\ndoes not prevent the local cleanup or change the 200 response.\n","tags":["OAuth"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"institutionId","required":true,"schema":{"type":"string"},"description":"ObjectId of the institution to disconnect."}],"responses":{"200":{"description":"Institution OAuth config cleared.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleOAuthRevokeResponse"}}}},"401":{"description":"Missing or invalid JWT (verifyToken)."},"403":{"description":"Caller does not hold `institutions.edit`.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Not authorized"}}}}}},"404":{"description":"Institution not found.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Institution not found"}}}}}},"500":{"description":"Unexpected error during revoke.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Failed to revoke institution Google services access"}}}}}}}}},"/api/auth/signin":{"post":{"summary":"User authentication and sign-in","description":"Authenticates a user with email and password, and returns a JWT token along with the user profile.\n\n**Rate Limiting:** 10 requests per minute per IP address.\n\n## JWT Token Lifecycle\n\nOn successful authentication, the response includes a `token` field containing a signed JWT.\n\n**Token payload:**\n- `_id` — User's unique identifier\n- `email` — User's email address\n- `customerId` — Stripe customer ID (if applicable)\n- `accountType` — One of `super`, `admin`, or `user`\n- `sessionId` — Server-side session identifier\n- `iat` — Issued-at timestamp (set automatically by JWT)\n- `exp` — Expiration timestamp (set automatically by JWT)\n\n**Token expiration:** 6 hours (21,600 seconds) by default. Configurable via `JWT_VALIDITY_SEC` environment variable.\n\n## Using the Token\n\nInclude the JWT in every subsequent API request using one of these methods (in priority order):\n\n1. **`x-access-token` header (recommended):**\n   ```\n   x-access-token: eyJhbGciOiJIUzI1NiIs...\n   ```\n\n2. **`Authorization` header with Bearer scheme:**\n   ```\n   Authorization: Bearer eyJhbGciOiJIUzI1NiIs...\n   ```\n\n3. **Query parameter:**\n   ```\n   GET /api/resource?token=eyJhbGciOiJIUzI1NiIs...\n   ```\n\n4. **Request body field:**\n   ```json\n   { \"token\": \"eyJhbGciOiJIUzI1NiIs...\" }\n   ```\n\n## Token Errors\n\nWhen a token is missing, expired, or invalid, the API returns:\n\n- **403** — No token provided (`Authentication Required`)\n- **401** — Token expired (`jwt expired`) or token invalid (`invalid signature`)\n\n## Token Renewal (Sliding Session)\n\nTokens are automatically refreshed via a sliding session mechanism. Each time the client calls\n`POST /api/user/refresh/profile`, the response includes a fresh JWT token with a new expiration.\nThis extends the session without requiring re-authentication, as long as the current token is still valid.\n\nThe frontend calls this endpoint on every page load, so active users never experience token expiration.\nIf the token expires (e.g., user is inactive for more than 6 hours), a new sign-in is required.\n","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignInRequest"}}}},"responses":{"200":{"description":"Successful authentication. Returns JWT token and user profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignInResponse"}}}},"400":{"description":"Bad request - missing required or invalid fields","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Password must be at least 6 characters long"},"success":{"type":"boolean","example":false}}}}}},"401":{"description":"Invalid credentials or inactive account","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Invalid Password !"},"token":{"type":"string","nullable":true,"example":null}}}}}},"403":{"description":"Account not activated - an activation email has been sent","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Check your email for a one-time activation link to complete your account setup."}}}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"The system can not sign you in at this time."}}}}}}}}},"/api/auth/autosignup":{"post":{"summary":"Automatic user signup and authentication","description":"Creates or authenticates a user account automatically based on CMS/LMS integration data.\nTypically called after SDK launch token verification (see `/api/auth/sdk-verify`).\n\n**Security:**\n- Rate limited to 10 requests per minute per IP\n- All new SDK signups are assigned `accountType: \"user\"` regardless of `lxp_user_type`\n- Admin promotion is handled server-side via the institution's `contactEmail` trust anchor\n- URL validation uses exact hostname comparison (not substring matching)\n- Default passwords are cryptographically random (not derivable from email)\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoSignupRequest"}}}},"responses":{"200":{"description":"Successful authentication with user profile and token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoSignupResponse"}}}},"400":{"description":"Bad request - invalid input data"},"401":{"description":"Unauthorized - invalid or missing authentication token"},"500":{"description":"Internal server error"}},"tags":["Authentication"]}},"/api/auth/createInstitutionForContextid":{"post":{"summary":"Create a new institution (Digital Twin) for a specific context URL","description":"Creates a new institution record with the provided configuration and context information.\nThe new institution's parent account is resolved in order: the explicit `account` id when provided; otherwise the first account whose `domainUrls` contains the supplied `domain`; otherwise the parent account of the `contactEmail` user's own institution. If none match, the institution is created without a parent account.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInstitutionRequest"}}}},"responses":{"200":{"description":"Institution created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInstitutionResponse"}}}},"400":{"description":"Bad request - Invalid input data"},"401":{"description":"Unauthorized - Invalid or missing access token"},"500":{"description":"Internal server error"}},"tags":["Institutions"]}},"/api/auth/generate-prompt-preview":{"post":{"summary":"Generate Digital Twin instructions from Q&A","description":"Takes interview question/answer pairs and generates AI-powered persona instructions\nfor a Digital Twin being created. Used during the creation wizard before the\ninstitution exists. Requires JWT authentication.\n","tags":["Authentication"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratePromptPreviewRequest"}}}},"responses":{"200":{"description":"Generated instructions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratePromptPreviewResponse"}}}},"400":{"description":"Invalid request or generation error"},"401":{"description":"Authentication required"}}}},"/api/auth/sdk-sign":{"post":{"summary":"Sign SDK launch parameters","description":"Signs launch parameters with a server-held HMAC-SHA256 secret for secure SDK iframe embedding.\nCalled by `pria-sdk.js` before creating the launch iframe.\n\n**Security model:**\n- The signing secret (`SDK_LAUNCH_SECRET`) is never exposed to the client\n- Parameters are canonicalized (all values stringified, `launch_*` keys stripped) to ensure\n  consistent HMAC computation across sign and verify, since URL query strings coerce values to strings\n- A cryptographic nonce and timestamp are included to prevent replay attacks\n- The token expires after 10 minutes\n\n**Origin validation:**\n- For institution-specific launches, the request `Origin` or `Referer` header is validated\n  against the institution's `publicAuthorizedUrls` using exact hostname comparison\n- In development mode (`NODE_ENV !== 'production'`), `file://` origins (null/missing Origin header) are allowed\n\n**Digital twin selector mode:**\n- When `institutionId` is an empty string and `params.digitaltwin` is true, institution lookup\n  and origin validation are skipped. The user will be presented with their existing digital twins\n  or the option to create a new one.\n","tags":["SDK Launch"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SdkSignRequest"},"examples":{"institutionLaunch":{"summary":"Standard institution launch","value":{"params":{"email":"john.doe@domain.edu","profilename":"John Doe","usertype":4,"userid":110,"institutionid":"f831501f-b645-481a-9cbb-331509aaf8c1","task":"do"},"institutionId":"f831501f-b645-481a-9cbb-331509aaf8c1"}},"digitalTwinSelector":{"summary":"Digital twin selector (no specific institution)","value":{"params":{"email":"john.doe@domain.edu","profilename":"John Doe","usertype":4,"digitaltwin":true,"task":"do","institutionid":""},"institutionId":""}}}}}},"responses":{"200":{"description":"Parameters signed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SdkSignResponse"}}}},"400":{"description":"Missing params/institutionId or invalid institution","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Invalid institution"}}}}}},"403":{"description":"Origin not authorized for this institution","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Unauthorized origin"}}}}}},"500":{"description":"Server configuration error (SDK_LAUNCH_SECRET not set)"}}}},"/api/auth/sdk-verify":{"post":{"summary":"Verify SDK launch token","description":"Verifies an HMAC-SHA256 launch token against the server-held secret.\nCalled by `Sdk.js` (React frontend) before proceeding to autosignup.\n\n**Verification steps:**\n1. Checks that the timestamp is within a 10-minute window\n2. Recomputes the HMAC from canonicalized params (values stringified, `launch_*` keys stripped)\n3. Compares using constant-time `crypto.timingSafeEqual` to prevent timing attacks\n\n**When verification fails:**\n- Expired tokens (>10 min) return 401 with \"Launch token expired\"\n- Tampered or invalid tokens return 401 with \"Invalid launch token\"\n","tags":["SDK Launch"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SdkVerifyRequest"}}}},"responses":{"200":{"description":"Token verified successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SdkVerifyResponse"}}}},"400":{"description":"Missing verification parameters","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Missing verification parameters"}}}}}},"401":{"description":"Token expired, invalid, or verification failed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Invalid launch token"}}}}}},"500":{"description":"Server configuration error (SDK_LAUNCH_SECRET not set)"}}}},"/api/auth/api-key-signin":{"post":{"summary":"Exchange a Pria API key for a JWT","description":"Validates the `x-api-key` header against the hashed key stored on the user record\nand returns a regular Pria JWT (`token`) plus a minimal `profile` envelope. The\nJWT is identical in shape and lifetime to the one issued by `POST /api/auth/signin`\n— every other authenticated endpoint accepts it via `Authorization: Bearer <jwt>`\nor `x-access-token: <jwt>`.\n\n**Important: the API key is NOT a JWT.** Sending the raw `pria_…` key as a\n`Authorization: Bearer` value will fail with `Invalid access token jwt malformed`\non the bearer-protected endpoints. You must do the exchange here first.\n\n**Authentication transport:** the API key MUST be sent in the `x-api-key` header,\nnot `Authorization`. The endpoint has no JWT gate — only the key check.\n\n**Access gating:**\n- Key format is enforced: `pria_` followed by 40 hex chars (`/^pria_[0-9a-f]{40}$/`).\n  A malformed key returns 401, not 400.\n- Lookup uses the 9-char prefix for indexing, then verifies the full SHA-256 hash.\n- Only users with `accountType` of `admin` or `super` (and `status !== 'deleted'`)\n  can mint a JWT. Demoting a user immediately disables their key.\n\n**Rate limiting:** 100 requests per minute per IP (the shared auth limiter).\n","tags":["Authentication"],"security":[{"priaApiKey":[]}],"parameters":[{"in":"header","name":"x-api-key","required":true,"schema":{"type":"string","pattern":"^pria_[0-9a-f]{40}$"},"description":"Pria API key (`pria_` + 40 hex chars). Provisioned by an admin via the admin UI.","example":"pria_0d59f32058727e990bbd5cbdac7668dc2e2c6c09"}],"responses":{"200":{"description":"Key accepted — JWT and minimal profile returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeySigninResponse"}}}},"400":{"description":"`x-api-key` header is missing or not a string.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"x-api-key header is required"}}}}}},"401":{"description":"Key is malformed, unknown, or the bound user is not admin/super (or is deleted).\nThe handler returns the same generic message for all three cases to avoid\nleaking which keys exist.\n","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Invalid API key"}}}}}},"429":{"description":"Too many requests (IP-level abuse limiter — 100/min)."},"500":{"description":"Internal server error during signin.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Sign-in failed: <error details>"}}}}}}}}},"/api/auth/token_complete":{"get":{"summary":"Complete the Canvas LMS API authorization handoff","description":"Canvas redirects the user's browser here after they authorize Pria to call the\nCanvas API on their behalf (Canvas OAuth2 authorization-code flow). The handler\nexchanges the supplied `code` for a Canvas API access token, stores it on the\ncaller's `UserInstitution.canvasApiToken`, writes a history entry recording the\nsuccessful authentication, and returns a small HTML page that closes the popup\nand signals the parent window to retry the request that triggered the auth.\n\nThis is **not** a Pria login flow — the user is already signed in with a valid\nPria JWT before being sent to Canvas. The `state` parameter carries the Pria\nuser's `_id` so the backend can look up which user to attach the Canvas token\nto without relying on the session.\n\n**Configuration requirements:** the user's institution (or its parent account)\nmust have `canvasClientId` and `canvasClientSecret` set, and a derivable Canvas\nAPI domain (an instructure.com URL or a `.edu` vanity URL in\n`publicAuthorizedUrls`).\n","tags":["OAuth"],"parameters":[{"in":"query","name":"code","schema":{"type":"string"},"required":true,"description":"Canvas OAuth authorization code to exchange for an access token."},{"in":"query","name":"state","schema":{"type":"string"},"required":true,"description":"Pria user `_id` — set during the original `consentUrl` generation so this\ncallback can find which Pria user to attach the Canvas token to.\n"},{"in":"query","name":"error","schema":{"type":"string"},"description":"Set by Canvas when the user denies the consent screen."},{"in":"query","name":"error_description","schema":{"type":"string"},"description":"Human-readable failure reason that accompanies `error`."}],"responses":{"200":{"description":"Canvas token stored successfully. Returns an HTML page (`TOKEN_SUCCESS_HTML`)\nthat closes the popup window and signals the opener to retry.\n","content":{"text/html":{"schema":{"type":"string"}}}},"400":{"description":"Canvas returned an OAuth error, required query parameters are missing,\nthe institution is misconfigured (no Canvas API URL / client id / secret),\nor the Canvas token exchange itself failed.\n","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Parameter code is required"},"error":{"type":"string","description":"Present on Canvas exchange failures (instead of `message`).","example":"Unknown error (3) <upstream>"}}}}}},"404":{"description":"User identified by `state` was not found, or the required UserInstitution\nentitlement row is missing.\n","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"User not found"}}}}}},"500":{"description":"Database error while loading the UserInstitution entitlement.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Error - can't find the user's entitlement"}}}}}}}}},"/api/auth/mfa-verify":{"post":{"summary":"Submit a 6-digit MFA code to complete login","description":"Second leg of the MFA login flow. The first leg (POST /api/auth/signin\nor /autosignup) returned `{mfaRequired: true, challengeId, maskedEmail}`\nand emailed a 6-digit code. This endpoint accepts the typed code, and\non success returns the same shape as a normal signin response (token +\nprofile) and sets a long-lived `pria_mfa_trust` cookie that lets the\nsame browser skip MFA for the next 7 days (or whatever MFA_TRUST_DAYS\nsets, bounded to [1, 30]).\n\n**Brute-force protection:** 5 wrong attempts per code; the 6th burns\nthe challenge (status invalidated) and the user must request a new\ncode via /mfa-resend. The signin gate further locks the account for\n10 minutes after 5 invalidated challenges (with at least one wrong\nattempt each) in the previous 1 hour.\n\n**Rate limit:** 100 req/min per IP (authLimiter).\n","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaVerifyRequest"}}}},"responses":{"200":{"description":"Code accepted. Response shape matches POST /api/auth/signin success — a JWT token plus the user profile. A Set-Cookie pria_mfa_trust=... header is also set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupResponse"}}}},"400":{"description":"Missing challengeId or code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaVerifyError"}}}},"401":{"description":"Wrong code — `attemptsRemaining` indicates how many tries remain before the challenge is invalidated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaVerifyError"}}}},"410":{"description":"Challenge unknown / expired / already verified / cancelled — client must restart the signin flow.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaVerifyError"}}}},"423":{"description":"Too many wrong attempts on this code; the challenge is invalidated. Request a fresh code via /mfa-resend.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaVerifyError"}}}}}}},"/api/auth/mfa-resend":{"post":{"summary":"Request a new MFA code for the current challenge","description":"Invalidates the current challenge and issues a fresh one with the\nsame loginContext (so the verify step still lands you in the same\nbranding / institution context). The new challenge has its own 5-min\nTTL and its own 5-attempt cap.\n\n**Cooldown:** the previous challenge must be at least 30 seconds old\nbefore a Resend is allowed — prevents email-bombing.\n\nResend-driven invalidations (where the user never submitted a wrong\ncode) do NOT count toward the per-account 5-burned-codes-in-1h\nlockout cap.\n","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaChallengeIdRequest"}}}},"responses":{"200":{"description":"New code emailed; client must use the newly-returned challengeId for subsequent verify / resend / cancel calls.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaResendResponse"}}}},"400":{"description":"Missing challengeId.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaVerifyError"}}}},"410":{"description":"Challenge unknown / expired / already verified / cancelled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaVerifyError"}}}},"429":{"description":"Resend cooldown active — `retryAfter` (seconds) indicates the remaining wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaVerifyError"}}}},"502":{"description":"Email provider failure — challenge is invalidated; client should restart the signin flow.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaVerifyError"}}}}}}},"/api/auth/mfa-cancel":{"post":{"summary":"Abandon the current MFA challenge (user clicked Cancel)","description":"Marks the challenge as invalidated server-side so it can no longer be\nused for /mfa-verify or /mfa-resend. Idempotent — calling it on an\nalready-cancelled / expired / verified challenge still returns 200.\n\nThe client should clear its `sessionStorage` copy of the challengeId\nand route the user back to the login screen.\n","tags":["Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaChallengeIdRequest"}}}},"responses":{"200":{"description":"Cancellation acknowledged.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true}}}}}},"400":{"description":"Missing challengeId.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaVerifyError"}}}}}}},"/api/admin/account":{"post":{"summary":"Create a new account","description":"Creates a new account in the system","tags":["Admin Accounts"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountCreateRequest"}}}},"responses":{"200":{"description":"Account created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountResponse"}}}},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"}}}},"/api/admin/account/{id}":{"get":{"summary":"Get account by ID","description":"Retrieves details of a specific account","tags":["Admin Accounts"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Account ID"}],"responses":{"200":{"description":"Account retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountResponse"}}}},"404":{"description":"Account not found"}}},"put":{"summary":"Update account","description":"Updates an existing account","tags":["Admin Accounts"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Account ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountCreateRequest"}}}},"responses":{"200":{"description":"Account updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"404":{"description":"Account not found"}}},"delete":{"summary":"Delete account","description":"Removes an account from the system","tags":["Admin Accounts"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Account ID"}],"responses":{"200":{"description":"Account deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"404":{"description":"Account not found"}}}},"/api/admin/accountInstitution/{id}":{"put":{"summary":"Transfer credits between account and institution","description":"Transfers credits from the parent account to one of its institutions (positive credits value) or reclaims credits from the institution back to the account (negative credits value). The institution must belong to the specified account. Both the account and institution must have sufficient credits for the transfer direction. An AccountTransfer record is created for audit purposes.","tags":["Admin Accounts"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Account ID (the parent account to transfer credits from/to)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["institutionId","credits"],"properties":{"institutionId":{"type":"string","description":"Institution ID to transfer credits to/from (must belong to the account)"},"credits":{"type":"number","description":"Number of credits to transfer. Positive = account to institution, negative = institution to account.","example":100}}}}}},"responses":{"200":{"description":"Credit transfer completed and recorded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"account":{"type":"object","properties":{"id":{"type":"string","description":"Account ID"},"credits":{"type":"number","description":"Updated account credit balance"}}},"institution":{"type":"object","properties":{"id":{"type":"string","description":"Institution ID"},"credits":{"type":"number","description":"Updated institution credit balance"},"creditsTotal":{"type":"number","description":"Total credits ever allocated to the institution (virtual field)"},"creditsUsagePct":{"type":"number","description":"Credit usage percentage (virtual field)"}}}}},"message":{"type":"string","example":"Account Transfer recorded!"}}}}}},"400":{"description":"Bad request - empty body, missing institutionId, missing credits, transfer recording failure, or general error","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string"}}}}}},"401":{"description":"Unauthorized - user not found","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Authentication Required"}}}}}},"403":{"description":"Forbidden - user is not a super admin or account manager for this account","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Unauthorized: not a manager for this account"}}}}}},"404":{"description":"Institution not found, account not found, institution does not belong to account, or insufficient credits","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string"}}}}}}}}},"/api/admin/accountTransfers":{"post":{"summary":"Get list of account transfers","description":"Retrieves account transfer history with optional filtering","tags":["Admin Accounts"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountTransferListRequest"}}}},"responses":{"200":{"description":"Account transfers retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountTransferListResponse"}}}}}}},"/api/admin/account/{id}/credits":{"post":{"summary":"Adjust an account's credit balance (super admin only)","description":"Adds or subtracts credits from the given account. Records an AccountTransfer with a mandatory note for audit. Reserved for super administrators - regular admins and account managers receive 403.","tags":["Admin Accounts"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Account ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["credits","note"],"properties":{"credits":{"type":"integer","description":"Non-zero integer. Positive adds, negative subtracts.","example":500},"note":{"type":"string","description":"Mandatory reason for the adjustment (e.g. invoice reference, compensation reason). Max 2000 chars.","example":"INV-2026-0412 annual renewal"}}}}}},"responses":{"200":{"description":"Credits adjusted and transfer recorded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"account":{"type":"object","properties":{"id":{"type":"string"},"credits":{"type":"number"}}},"transfer":{"$ref":"#/components/schemas/AccountTransfer"}}},"message":{"type":"string","example":"Account credits adjusted!"}}}}}},"400":{"description":"Validation failure (invalid id, credits, or note) or insufficient balance"},"401":{"description":"Authentication required"},"403":{"description":"Not a super admin"},"404":{"description":"Account not found"}}}},"/api/admin/aimodel":{"post":{"summary":"Create a new AI model","description":"Creates a new AI model configuration. The handler validates the `client_library` / `api_url` / `api_key` combination via `validateAIModelConfig` before saving — openai_cli requires both `api_url` and `api_key`; mistral_cli and xai_cli require `api_key`; bedrock_cli requires neither (uses AWS credentials). When `institution` is omitted, the caller's institution is used.","tags":["Admin AI Models"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AIModelCreateRequest"}}}},"responses":{"201":{"description":"AI model created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AIModelCreateResponse"}}}},"400":{"description":"Missing properties, invalid api_url / api_key for the chosen client_library, duplicate name, or other validation failure."},"401":{"description":"Authentication required / invalid request."},"403":{"description":"Caller is not an admin or super user."}}}},"/api/admin/aimodel/{id}":{"get":{"summary":"Get AI model by ID","description":"Retrieves details of a specific AI model, with the `institution` field populated to `{ _id, name, ainame, picture }`.","tags":["Admin AI Models"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"AI Model ID"}],"responses":{"200":{"description":"AI model retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AIModelResponse"}}}},"400":{"description":"Invalid AI Model ID."},"401":{"description":"Authentication required."},"403":{"description":"Caller is not an admin or super user."},"404":{"description":"AI model not found."}}},"put":{"summary":"Update AI model","description":"Partially updates an existing AI model configuration. Only fields present in the request body are written. The handler merges `client_library` / `api_url` / `api_key` with the stored document and re-validates the resulting combination before persisting.","tags":["Admin AI Models"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"AI Model ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AIModelUpdateRequest"}}}},"responses":{"200":{"description":"AI model updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid AI Model ID, empty body, or api_url / api_key validation failure for the resolved client_library."},"401":{"description":"Authentication required."},"403":{"description":"Caller is not an admin or super user."},"404":{"description":"AI model not found."}}},"delete":{"summary":"Delete AI model","description":"Soft-deletes an AI model. The handler prefixes the document's `name` with the current timestamp and sets `status` to `deleted`; the record is not physically removed.","tags":["Admin AI Models"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"AI Model ID"}],"responses":{"200":{"description":"AI model deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid AI Model ID."},"401":{"description":"Authentication required."},"403":{"description":"Caller is not an admin or super user."},"404":{"description":"AI model not found."}}}},"/api/admin/aimodels":{"post":{"summary":"Get list of AI models","description":"Retrieves AI models with optional filtering. Two response shapes:\n\n- **Paginated mode (default):** Returns `{ success, data, total, hasMore, page, pageSize, message }`. The `institution` field on each item is populated to `{ name, ainame, picture }`.\n- **Minimum mode (`minimum: true`):** Returns `{ success, data, message }` with `data` projected to `{ _id, name, model_use, status }`. No pagination fields. Used by dropdown selectors.\n\nFilter precedence: `institution` > `account` (space-separated IDs, expanded to their institutions and filtered by the caller's RAP) > caller's own institution (for admins) > all non-deleted models (for supers).\n","tags":["Admin AI Models"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AIModelListRequest"}}}},"responses":{"200":{"description":"AI models retrieved successfully. Shape depends on the `minimum` flag in the request.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/AIModelListResponse"},{"$ref":"#/components/schemas/AIModelListMinimumResponse"}]}}}},"400":{"description":"Query / RAP check failure."},"401":{"description":"Authentication required."},"403":{"description":"Caller is not an admin or super user."}}}},"/api/admin/feedback/{id}":{"get":{"summary":"Get feedback by ID","description":"Retrieves details of a specific feedback","tags":["Admin Feedbacks"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Feedback ID"}],"responses":{"200":{"description":"Feedback retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminFeedbackResponse"}}}},"404":{"description":"Feedback not found"}}},"put":{"summary":"Update feedback","description":"Updates feedback status and admin notes","tags":["Admin Feedbacks"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Feedback ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminFeedbackUpdateRequest"}}}},"responses":{"200":{"description":"Feedback updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"404":{"description":"Feedback not found"}}},"delete":{"summary":"Delete feedback","description":"Removes a feedback record","tags":["Admin Feedbacks"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Feedback ID"}],"responses":{"200":{"description":"Feedback deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"404":{"description":"Feedback not found"}}}},"/api/admin/institution":{"post":{"summary":"Create a new institution","description":"Creates a new institution in the system","tags":["Admin Institutions"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstitutionCreateRequest"}}}},"responses":{"200":{"description":"Institution created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstitutionCreateResponse"}}}},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"}}}},"/api/admin/institution/{id}":{"delete":{"summary":"Delete an institution","description":"Removes an institution from the system","tags":["Admin Institutions"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Institution ID"}],"responses":{"200":{"description":"Institution deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"404":{"description":"Institution not found"}}}},"/api/admin/generateAbout":{"post":{"summary":"Generate about text for institution","description":"Uses AI to generate an about section based on institution questions","tags":["Admin Institutions"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateAboutRequest"}}}},"responses":{"200":{"description":"About text generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateAboutResponse"}}}}}}},"/api/admin/freeInstitutionCreditsById/{id}":{"put":{"summary":"Award free credits to institution","description":"Awards free credits to a specific institution","tags":["Admin Institutions"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Institution ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FreeCreditsRequest"}}}},"responses":{"200":{"description":"Credits awarded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"404":{"description":"Institution not found"}}}},"/api/admin/cloneInstitution/{id}":{"post":{"summary":"Clone an institution","description":"Creates a copy of an existing institution with all settings","tags":["Admin Institutions"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Source Institution ID to clone"}],"responses":{"200":{"description":"Institution cloned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloneInstitutionResponse"}}}},"404":{"description":"Source institution not found"}}}},"/api/admin/institution/{id}/account":{"patch":{"summary":"Reassign an institution to a different account","description":"Clean-room endpoint with strict field whitelisting. Only the `account` field is accepted.\nNon-super users must have access to both the source institution and the target account.\nPassing `account: null` detaches the institution from its parent account — super users only.\n","tags":["Admin Institutions"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Institution ID to reassign"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInstitutionAccountRequest"}}}},"responses":{"200":{"description":"Institution account updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInstitutionAccountResponse"}}}},"401":{"description":"Authentication required"},"403":{"description":"Not authorized (missing scope or target account access)"},"404":{"description":"Institution or target account not found"}}}},"/api/admin/generateInstitutionPrompt":{"post":{"summary":"Generate institution system prompt","description":"Generates a system prompt based on institution questions","tags":["Admin Questions"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratePromptRequest"}}}},"responses":{"200":{"description":"Prompt generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratePromptResponse"}}}}}}},"/api/admin/analyzePrompt":{"post":{"summary":"Analyze a prompt","description":"Analyzes a prompt for quality and provides suggestions","tags":["Admin Questions"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzePromptRequest"}}}},"responses":{"200":{"description":"Prompt analyzed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzePromptResponse"}}}}}}},"/api/admin/deleteHistories":{"post":{"summary":"Delete multiple histories","description":"Deletes multiple history records based on criteria","tags":["Admin Histories"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"},"description":"Array of history IDs to delete"},"institution":{"type":"string","description":"Delete all histories for institution"},"user":{"type":"string","description":"Delete all histories for user"}}}}}},"responses":{"200":{"description":"Histories deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"deletedCount":{"type":"integer","description":"Number of records deleted"}}}}}}}}},"/api/admin/chart/{id}":{"get":{"summary":"Get chart by ID","description":"Retrieves a specific chart","tags":["Admin Charts"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Chart ID"}],"responses":{"200":{"description":"Chart retrieved successfully"},"404":{"description":"Chart not found"}}},"delete":{"summary":"Delete chart","description":"Removes a chart","tags":["Admin Charts"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Chart ID"}],"responses":{"200":{"description":"Chart deleted successfully"},"404":{"description":"Chart not found"}}}},"/api/admin/charts":{"post":{"summary":"Get list of charts","description":"Retrieves charts with optional filtering","tags":["Admin Charts"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChartListRequest"}}}},"responses":{"200":{"description":"Charts retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChartListResponse"}}}}}}},"/api/admin/institution/{id}/credits":{"post":{"summary":"Adjust an institution's credit balance (super admin only)","description":"Adds or subtracts credits from the given institution. Records an AccountTransfer with a mandatory note for audit. If the institution has a parent account, by default credits are transferred zero-sum from that account (the account's balance is decremented by the same amount and the AccountTransfer record has both institution and account set). For standalone institutions OR when skipParentDebit is true, credits are injected directly into the institution with no account adjustment, and the AccountTransfer record omits the account field. skipParentDebit is intended for promo/compensation credits funded by the platform. Reserved for super administrators - regular admins and account managers receive 403.","tags":["Admin Institutions"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Institution ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["credits","note"],"properties":{"credits":{"type":"integer","description":"Non-zero integer. Positive adds, negative subtracts.","example":100},"note":{"type":"string","description":"Mandatory reason for the adjustment. Max 2000 chars.","example":"compensation for outage"},"skipParentDebit":{"type":"boolean","default":false,"description":"If true, inject credits directly into the institution without debiting the parent account. Only meaningful when the institution has a parent account. Intended for promo/compensation credits funded by the platform rather than the client account."}}}}}},"responses":{"200":{"description":"Credits adjusted and transfer recorded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"institution":{"type":"object","properties":{"id":{"type":"string"},"credits":{"type":"number"}}},"account":{"type":"object","description":"Present only on zero-sum transfers (parent account existed and skipParentDebit was not set).","properties":{"id":{"type":"string"},"credits":{"type":"number"}}},"transfer":{"$ref":"#/components/schemas/AccountTransfer"}}},"message":{"type":"string","example":"Institution credits adjusted!"}}}}}},"400":{"description":"Validation failure (invalid id, credits, or note) or insufficient balance"},"401":{"description":"Authentication required"},"403":{"description":"Not a super admin"},"404":{"description":"Institution not found"},"409":{"description":"Parent account reference is dangling - resolve before adjusting credits"}}}},"/api/admin/institution/{id}/agent":{"get":{"summary":"Get agent workspace state for an instance","description":"Returns the desired agent workspace state for one Digital Twin instance. Requires admin access and institutions.edit RAP for the target instance.","tags":["Admin Institutions"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Institution ID"}],"responses":{"200":{"description":"Agent workspace state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentWorkspaceAgentResponse"}}}},"400":{"description":"Invalid instance id"},"403":{"description":"Missing admin or RAP authorization"}}}},"/api/admin/institution/{id}/agent/activate":{"post":{"summary":"Make an instance an agent","description":"Enables agent desired state for one Digital Twin instance and starts workspace provisioning using the configured backend mode. The request body is whitelisted; runtime fields such as desktopPath and externalId are server-owned.","tags":["Admin Institutions"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Institution ID"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentWorkspaceActivationRequest"}}}},"responses":{"200":{"description":"Activation accepted or idempotently already active","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentWorkspaceAgentResponse"}}}},"400":{"description":"Invalid instance id or manifest"},"403":{"description":"Missing admin or RAP authorization"}}}},"/api/admin/institution/{id}/agent/retry":{"post":{"summary":"Retry failed agent workspace provisioning","description":"Re-runs provisioning only when the current agent status is failed; other lifecycle states are idempotent no-ops.","tags":["Admin Institutions"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Institution ID"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentWorkspaceActivationRequest"}}}},"responses":{"200":{"description":"Retry accepted, completed, failed, or idempotently skipped","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentWorkspaceAgentResponse"}}}},"400":{"description":"Invalid instance id or manifest"},"403":{"description":"Missing admin or RAP authorization"}}}},"/api/admin/institution/{id}/agent/disable":{"post":{"summary":"Disable agent workspace desired state","description":"Marks the instance agent workspace as disabled. MVP disable does not stop existing ECS tasks or delete EFS access points; operators must reap runtime resources separately until lifecycle reaping is implemented.","tags":["Admin Institutions"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Institution ID"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentWorkspaceDisableRequest"}}}},"responses":{"200":{"description":"Agent desired state disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentWorkspaceAgentResponse"}}}},"403":{"description":"Missing admin or RAP authorization"}}}},"/api/admin/mcpserver":{"post":{"summary":"Create a new MCP server","description":"Creates a new Model Context Protocol server configuration","tags":["Admin MCP Servers"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MCPServerCreateRequest"}}}},"responses":{"200":{"description":"MCP server created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MCPServerResponse"}}}},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"}}}},"/api/admin/mcpserver/{id}":{"get":{"summary":"Get MCP server by ID","description":"Retrieves details of a specific MCP server","tags":["Admin MCP Servers"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"MCP Server ID"}],"responses":{"200":{"description":"MCP server retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MCPServerResponse"}}}},"404":{"description":"MCP server not found"}}},"put":{"summary":"Update MCP server","description":"Updates an existing MCP server configuration","tags":["Admin MCP Servers"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"MCP Server ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MCPServerCreateRequest"}}}},"responses":{"200":{"description":"MCP server updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"404":{"description":"MCP server not found"}}},"delete":{"summary":"Delete MCP server","description":"Removes an MCP server configuration","tags":["Admin MCP Servers"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"MCP Server ID"}],"responses":{"200":{"description":"MCP server deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"404":{"description":"MCP server not found"}}}},"/api/admin/mcpservers":{"post":{"summary":"Get list of MCP servers","description":"Retrieves all MCP servers with optional filtering","tags":["Admin MCP Servers"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MCPServerListRequest"}}}},"responses":{"200":{"description":"MCP servers retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MCPServerListResponse"}}}}}}},"/api/admin/memory/user/{userId}":{"get":{"tags":["Admin Memory"],"summary":"List memory parameters for a user, grouped by institution.","description":"Returns memory rows visible to the requesting admin. Super sees every\nrow for the target. Non-super admins see rows in institutions where\nthey hold users.edit, plus the target's personal rows iff they hold\nusers.edit on at least one institution where the target has membership.\n","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string"}},{"in":"query","name":"namespace","schema":{"type":"string"}},{"in":"query","name":"shared","schema":{"type":"string","enum":["true","false"]}},{"in":"query","name":"search","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"user":{"type":"object","properties":{"_id":{"type":"string"},"email":{"type":"string"},"fname":{"type":"string"},"lname":{"type":"string"}}},"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminMemoryRow"}}}}}}}}},"delete":{"tags":["Admin Memory"],"summary":"Bulk hard-delete memory parameters for a user (filter-aware).","description":"When `ids` is provided, deletes only those rows after re-checking auth\nper row. Otherwise filters by namespace/shared.\n","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"}},"namespace":{"type":"string"},"shared":{"type":"string","enum":["true","false"]}}}}}},"responses":{"200":{"description":"OK"}}}},"/api/admin/memory/institution/{instId}":{"get":{"tags":["Admin Memory"],"summary":"List shared memory parameters for an instance.","description":"Requires users.edit on the target institution (super bypasses).","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"instId","required":true,"schema":{"type":"string"}},{"in":"query","name":"namespace","schema":{"type":"string"}},{"in":"query","name":"search","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"institution":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"ainame":{"type":"string"}}},"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminInstitutionMemoryRow"}}}}}}}}},"delete":{"tags":["Admin Memory"],"summary":"Bulk hard-delete shared memory parameters for an instance (filter-aware).","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"instId","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"}},"namespace":{"type":"string"}}}}}},"responses":{"200":{"description":"OK"}}}},"/api/admin/memory/{id}":{"patch":{"tags":["Admin Memory"],"summary":"Edit a single memory parameter (whitelisted fields only).","description":"Whitelist: key_value, key_description, shared. key_name and key_namespace\ncannot be edited via PATCH because they are part of the unique index;\nto rename, delete + recreate.\n","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"key_value":{"type":"string"},"key_description":{"type":"string"},"shared":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/AdminMemoryRow"}}}}}}}},"delete":{"tags":["Admin Memory"],"summary":"Hard-delete a single memory parameter.","security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"deleted":{"type":"integer"}}}}}}}}},"/api/admin/session/{id}":{"get":{"summary":"Get session by ID","description":"Retrieves a single session by Mongo ObjectId. user and institution are populated with trimmed projections.","tags":["Admin Sessions"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Session ID (Mongo ObjectId)"}],"responses":{"200":{"description":"Session retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"400":{"description":"Bad request — missing or malformed session id, or an unexpected error was thrown","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Invalid Session Id"}}}}}},"401":{"description":"Unauthorized — the authenticated user was not found","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Authentication required"}}}}}},"404":{"description":"Session not found","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Session not found"}}}}}}}},"delete":{"summary":"Soft-delete a session","description":"Sets the session's status to 'deleted' (soft delete tombstone). The row is preserved in the database.","tags":["Admin Sessions"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Session ID (Mongo ObjectId)"}],"responses":{"200":{"description":"Session deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Session deleted!"}}}}}},"400":{"description":"Bad request — missing or malformed session id, or an unexpected error was thrown","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Invalid Session Id"}}}}}},"401":{"description":"Unauthorized — the authenticated user was not found","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Authentication required"}}}}}},"404":{"description":"Session not found","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Session not found"}}}}}}}}},"/api/admin/ssoProviders":{"post":{"summary":"List SSO providers","description":"Returns all SSO providers scoped by RAP. Client secrets are masked.","tags":["Admin - SSO Providers"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSOProviderListRequest"}}}},"responses":{"200":{"description":"List of SSO providers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSOProviderListResponse"}}}},"403":{"description":"Admin access required"}}}},"/api/admin/ssoProvider":{"post":{"summary":"Create SSO provider","description":"Creates a new SSO provider for an institution. One provider per institution.","tags":["Admin - SSO Providers"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSOProviderCreateRequest"}}}},"responses":{"201":{"description":"SSO provider created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"id":{"type":"string"},"message":{"type":"string"}}}}}},"400":{"description":"Validation error or duplicate"},"403":{"description":"Admin access required"}}}},"/api/admin/ssoProvider/{id}":{"get":{"summary":"Get SSO provider by ID","tags":["Admin - SSO Providers"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"SSO provider details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/SSOProvider"}}}}}},"404":{"description":"Provider not found"}}},"put":{"summary":"Update SSO provider","description":"Updates an SSO provider. Institution cannot be changed.","tags":["Admin - SSO Providers"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSOProviderCreateRequest"}}}},"responses":{"200":{"description":"Provider updated"},"400":{"description":"Validation error"},"404":{"description":"Provider not found"}}},"delete":{"summary":"Delete SSO provider","tags":["Admin - SSO Providers"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Provider deleted"},"404":{"description":"Provider not found"}}}},"/api/admin/ssoProvider/test/{id}":{"post":{"summary":"Test SSO provider connectivity","description":"Attempts to reach the token endpoint to verify configuration.","tags":["Admin - SSO Providers"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Test result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"reachable":{"type":"boolean"},"status":{"type":"integer"},"message":{"type":"string"}}}}}}}}},"/api/admin/institutions":{"post":{"summary":"Retrieve list of institutions","description":"Get a list of all institutions with their details and statistics","tags":["Admin Institutions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstitutionListRequest"},"example":{"minimum":true}}}},"responses":{"200":{"description":"Successfully retrieved institutions list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstitutionListResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication token"},"500":{"description":"Internal server error"}},"security":[{"apiKeyAuth":[]}]}},"/api/admin/institution/{institutionId}":{"get":{"summary":"Get institution details by ID","description":"Retrieves detailed information about a specific institution including configuration, credits, and settings","tags":["Admin Institutions"],"parameters":[{"in":"path","name":"institutionId","required":true,"schema":{"type":"string"},"description":"The institution ID"}],"responses":{"200":{"description":"Institution details retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstitutionResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token"},"404":{"description":"Institution not found"},"500":{"description":"Internal server error"}},"security":[{"apiKeyAuth":[]}]},"put":{"summary":"Update an institution configuration","description":"Updates the configuration settings for a specific institution","tags":["Admin Institutions"],"parameters":[{"in":"path","name":"institutionId","required":true,"schema":{"type":"string"},"description":"The institution ID","example":"665653e9f7e4f4f1b5c6ef1b"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstitutionUpdateRequest"}}}},"responses":{"200":{"description":"Institution successfully updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstitutionUpdateResponse"}}}},"400":{"description":"Bad request - Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessError"}}}},"401":{"description":"Unauthorized - Invalid or missing access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessError"}}}},"404":{"description":"Institution not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessError"}}}}},"security":[{"apiKeyAuth":[]}]}},"/api/admin/institutionQuestions":{"post":{"summary":"Retrieve institution questions","description":"Get questions associated with a specific institution, with option for minimum data","tags":["Admin Questions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstitutionQuestionsRequest"}}}},"responses":{"200":{"description":"Successfully retrieved institution questions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstitutionQuestionsResponse"}}}}},"security":[{"apiKeyAuth":[]}]}},"/api/admin/questions":{"post":{"summary":"Retrieve institutional assessment questions","description":"Fetches a list of questions used for institutional assessments, with optional filtering for minimal data","tags":["Admin Questions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionRequest"},"example":{"minimum":true,"questionType":"PERSONA"}}}},"responses":{"200":{"description":"Successfully retrieved questions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionsResponse"},"example":{"success":true,"data":[{"_id":"6657cb7f4f9e80ae6b52f9f1","code":"PERSONA_01","question":"Your full name","objectif":"Tell us who you are so your twin can introduce itself.","required":true,"position":1,"status":"active","created":"2024-05-30T00:42:39.441Z","section":1,"mbti":false,"type":"text"}]}}}},"400":{"description":"Bad Request - Invalid request parameters"},"401":{"description":"Unauthorized - Invalid or missing access token"},"500":{"description":"Internal Server Error"}},"security":[{"apiKeyAuth":[]}]}},"/api/admin/assistants":{"post":{"summary":"Retrieve filtered list of assistants","description":"Fetches a list of AI assistants based on specified filters, with optional minimal data return","tags":["Admin Assistants"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssistantFilterRequest"},"example":{"minimum":true,"institution":"665653e9f7e4f4f1b5c6ef1b"}}}},"responses":{"200":{"description":"Successfully retrieved assistants list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssistantListResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token"},"403":{"description":"Forbidden - Insufficient permissions for admin endpoint"},"500":{"description":"Internal server error"}},"security":[{"apiKeyAuth":[]}]}},"/api/admin/histories":{"post":{"summary":"Retrieve conversation histories for admin users","description":"Fetches conversation history data filtered by institution, user search term, and result limit","tags":["Admin Histories"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HistoriesRequest"}}}},"responses":{"200":{"description":"Successfully retrieved conversation histories","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HistoriesResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token"},"403":{"description":"Forbidden - Insufficient permissions"},"500":{"description":"Internal server error"}}}},"/api/admin/history/{historyId}":{"delete":{"summary":"Delete a specific history record","description":"Removes a history entry from the system using its unique identifier","tags":["Admin Histories"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"historyId","required":true,"schema":{"type":"string","pattern":"^[a-f\\d]{24}$"},"description":"The unique identifier of the history record to delete","example":"68b2e661e4769d5cc4d5817d"}],"responses":{"200":{"description":"History record successfully deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteHistoryResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token"},"404":{"description":"History record not found"},"500":{"description":"Internal server error"}}},"get":{"summary":"Retrieve detailed history record by ID","description":"Get comprehensive information about a specific conversation history record including user details, institution info, usage metrics, and conversation data","tags":["Admin Histories"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"historyId","required":true,"schema":{"type":"string"},"description":"The unique identifier of the history record","example":"68b6c5a8c0b11ad3ebf901d6"}],"responses":{"200":{"description":"History record retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HistoryResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token"},"404":{"description":"History record not found"},"500":{"description":"Internal server error"}}}},"/api/admin/histories/charts/weekly":{"post":{"summary":"Get weekly chart data for admin histories","description":"Retrieves chart data for administrative history tracking with configurable granularity and time range","tags":["Admin Histories"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChartRequest"}}}},"responses":{"200":{"description":"Successfully retrieved chart data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChartResponse"}}}},"400":{"description":"Bad Request - Invalid parameters","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Invalid request parameters"}}}}}},"401":{"description":"Unauthorized - Invalid or missing access token","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Unauthorized access"}}}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Internal server error"}}}}}}}}},"/api/admin/histories/charts/timeline":{"post":{"summary":"Get time-series chart data for histories","description":"Optimized endpoint for time-series charts (bar, line, area, heatmap, scatter). Supports count, credits, usage, cached, and price dimensions. Uses a 2-step top-N query to avoid fetching data for all institutions.\n","tags":["Admin Histories"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimelineChartRequest"}}}},"responses":{"200":{"description":"Successfully retrieved timeline chart data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChartResponse"}}}},"400":{"description":"Invalid parameters"},"401":{"description":"Unauthorized"}}}},"/api/admin/histories/charts/totals":{"post":{"summary":"Get totals chart data for histories","description":"Optimized endpoint for total/summary charts (pie, donut, radialBar, radar, polarArea). No time bucketing — returns aggregated totals per institution sorted by value. Top-N handled entirely in MongoDB via $sort + $limit.\n","tags":["Admin Histories"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotalsChartRequest"}}}},"responses":{"200":{"description":"Successfully retrieved totals chart data","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TotalsChartDataPoint"}}}}}}},"400":{"description":"Invalid parameters"},"401":{"description":"Unauthorized"}}}},"/api/admin/histories/charts/users":{"post":{"summary":"Get unique user count chart data","description":"Dedicated endpoint for dimension=users queries. Uses a double-$group pipeline to count unique users per time bucket and institution.\n","tags":["Admin Histories"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsersChartRequest"}}}},"responses":{"200":{"description":"Successfully retrieved user count chart data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChartResponse"}}}},"400":{"description":"Invalid parameters"},"401":{"description":"Unauthorized"}}}},"/api/admin/histories/charts/user-stats":{"post":{"summary":"Long-format per-user-per-period usage extract","description":"Aggregates query count and credit usage per `(user, institution, period)` for admin reporting / CSV export. One row per non-zero usage bucket. Capped at 5000 rows; the response sets `truncated: true` when the cap is reached. Excludes histories that the user has flagged as `forgotten`.\n","tags":["Admin Histories"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserStatsRequest"}}}},"responses":{"200":{"description":"Successful aggregation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserStatsResponse"}}}},"400":{"description":"Invalid parameters (e.g. unknown granularity)"},"401":{"description":"Unauthorized"},"408":{"description":"Aggregation exceeded the server-side time budget. Response carries `code: \"aggregation_timeout\"`; narrow the filter and retry."}}}},"/api/admin/histories/charts/cross-institution":{"post":{"summary":"Compare specific institutions side-by-side","description":"Runs parallel per-institution aggregation queries for side-by-side comparison. Requires explicit institution IDs. Optionally includes a total series. Returns pre-resolved institution names for display.\n","tags":["Admin Histories"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrossInstitutionChartRequest"}}}},"responses":{"200":{"description":"Successfully retrieved cross-institution chart data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrossInstitutionChartResponse"}}}},"400":{"description":"Invalid parameters or no institutions provided"},"401":{"description":"Unauthorized"}}}},"/api/admin/users":{"post":{"summary":"Search and retrieve users for admin management","description":"Retrieves a list of users based on search criteria including institution, account type, and search terms","security":[{"apiKeyAuth":[]}],"tags":["Admin Users"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSearchRequest"}}}},"responses":{"200":{"description":"Successfully retrieved user list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserListResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token"},"403":{"description":"Forbidden - Insufficient permissions"},"500":{"description":"Internal server error"}}}},"/api/admin/users/cap-effectiveness":{"post":{"summary":"Live 24h cap-effectiveness analytics","description":"For every institution in the caller's scope that has an ACTIVE per-user 24-hour credit cap, reports how many members are at/over their cap right now (reached) versus under it. Scope is resolved exactly like the user list (institution → account → the institutions the caller administers). Usage is the pooled 24h credit sum per member; members with no recorded pooled usage count as under the cap.","security":[{"apiKeyAuth":[]}],"tags":["Admin Users"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"account":{"type":"string","description":"Space-separated account id(s) to scope to."},"institution":{"type":"string","description":"Space-separated institution id(s) to scope to (takes precedence over account)."},"usersearch":{"type":"string","description":"Case-insensitive substring matched against member email / first / last name to narrow the membership tally."},"accountType":{"type":"string","description":"Restrict the membership tally to members with this account type."}}}}}},"responses":{"200":{"description":"Cap-effectiveness report.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"institutionId":{"type":"string"},"institutionName":{"type":"string"},"account":{"type":"string","nullable":true},"cap":{"type":"number","description":"The active per-user 24h cap for this institution."},"members":{"type":"integer"},"reached":{"type":"integer","description":"Members whose pooled 24h usage is at or over the cap."},"under":{"type":"integer"},"reachedPct":{"type":"integer","description":"Percentage of members at/over the cap (0-100)."}}}},"totals":{"type":"object","properties":{"institutions":{"type":"integer"},"members":{"type":"integer"},"reached":{"type":"integer"},"reachedPct":{"type":"integer"}}},"generatedAt":{"type":"string","format":"date-time"}}}}}},"401":{"description":"Unauthorized - Invalid or missing access token"},"403":{"description":"Forbidden - Insufficient permissions"},"408":{"description":"Aggregation timed out - narrow the filter and retry."},"500":{"description":"Internal server error"}}}},"/api/admin/user/{userId}":{"get":{"summary":"Get user details by ID (Admin only)","description":"Retrieves detailed information about a specific user. Requires admin privileges.","tags":["Admin Users"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string"},"description":"The user ID to retrieve","example":"66c78d1bdafac05a3c28bbce"}],"responses":{"200":{"description":"User details retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token"},"403":{"description":"Forbidden - Insufficient privileges"},"404":{"description":"User not found"}}},"delete":{"summary":"Delete a user by ID","description":"Permanently removes a user from the system. This is an admin-only operation.","tags":["Admin Users"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"},"description":"The unique identifier of the user to delete (MongoDB ObjectId)","example":"6750887f53025010ad53a642"}],"responses":{"200":{"description":"User successfully deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteUserResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token"},"403":{"description":"Forbidden - Insufficient permissions (admin access required)"},"404":{"description":"User not found"},"500":{"description":"Internal server error"}}}},"/api/admin/user":{"post":{"summary":"Create a new user in the admin system. Users get 15 credits when created. When  plan set to 'sdk' with a valid institution (your digital twin _id), user is automatically enrolled into the instance","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserRequest"}}}},"responses":{"201":{"description":"User created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserResponse"}}}}},"security":[{"apiKeyAuth":[]}],"tags":["Admin Users"]}},"/api/admin/userInstitutionsByUserId/{userId}":{"get":{"summary":"Get user institutions by user ID","description":"Retrieves all institutions associated with a specific user ID (admin endpoint)","tags":["Admin Entitlements"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string"},"description":"The user ID to get institutions for","example":"66c78d1bdafac05a3c28bbce"}],"responses":{"200":{"description":"User institutions found successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInstitutionsResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token"},"404":{"description":"User not found"},"500":{"description":"Internal server error"}}}},"/api/admin/userInstitutions":{"post":{"summary":"Get users associated with institutions","description":"Retrieves a list of users and their relationships with institutions based on search criteria","tags":["Admin Entitlements"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInstitutionRequest"}}}},"responses":{"200":{"description":"Successfully retrieved user institution data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInstitutionResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token"},"403":{"description":"Forbidden - Insufficient permissions"},"500":{"description":"Internal server error"}}}},"/api/admin/userInstitution/{userId}":{"put":{"summary":"Update user institution attributes such as account type, status, etc.","description":"Updates the account type for a specific user in the institution","tags":["Admin Entitlements"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string"},"description":"The unique identifier of the user","example":"66fb7821c0bfac24ba65fd07"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInstitutionUpdateRequest"}}}},"responses":{"200":{"description":"User institution successfully updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInstitutionUpdateResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication token"},"403":{"description":"Forbidden - Insufficient permissions"},"404":{"description":"User not found"},"500":{"description":"Internal server error"}}}},"/api/admin/userInstitution/{id}":{"delete":{"summary":"Delete a user institution by ID","description":"Removes a user institution from the system. Requires admin privileges.","tags":["Admin Entitlements"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"},"description":"The unique identifier of the user institution (MongoDB ObjectId)","example":"67826dfe5d387207aabd6fc1"}],"responses":{"200":{"description":"User institution successfully deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid User Institution - The provided ID does not correspond to a valid user institution","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Insufficient privileges (admin access required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"User institution not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/admin/userInstitutions/eligible":{"post":{"summary":"Get users eligible to enroll in an institution","description":"Returns users who are not yet members of the specified institution. Results are capped at 100. If no search term is provided, returns the first 100 eligible users. With a search term (2+ chars), filters by name or email.","tags":["Admin Entitlements"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EligibleUsersRequest"}}}},"responses":{"200":{"description":"Successfully retrieved eligible users","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EligibleUsersResponse"}}}},"400":{"description":"Bad request - missing institution or server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Insufficient privileges","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/admin/userInstitutions/enroll":{"post":{"summary":"Enroll users into an institution","description":"Creates user-institution memberships for the specified users. Skips users who are already enrolled or invalid. Requires users.add entitlement for the target institution.","tags":["Admin Entitlements"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrollUsersRequest"}}}},"responses":{"201":{"description":"Users enrolled successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrollUsersResponse"}}}},"400":{"description":"Bad request - missing fields or server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Insufficient privileges","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/admin/sessions/charts":{"post":{"summary":"Get session analytics charts","description":"Returns distribution charts for OS, browser, device type, daily activity timeline, and a day-of-week/hour-of-day peak usage heatmap. Results are cached. When more than 100k sessions match the filter and no institution/account filter is set, the date range is auto-narrowed to 3 days for memory safety. Distribution charts are capped at 100k most-recent sessions.\n","tags":["Admin Sessions"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"institution":{"type":"string","description":"Space-separated institution IDs to filter"},"account":{"type":"string","description":"Space-separated account IDs to filter"},"daterange":{"type":"array","items":{"type":"string","format":"date"},"minItems":2,"maxItems":2,"description":"[startDate, endDate] — defaults to last 7 days if omitted"}}}}}},"responses":{"200":{"description":"Successfully retrieved session chart data","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"os":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"count":{"type":"integer"}}}},"browser":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"count":{"type":"integer"}}}},"deviceType":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"count":{"type":"integer"}}}},"activity":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","description":"YYYY-MM-DD formatted date"},"count":{"type":"integer"}}}},"heatmap":{"type":"array","items":{"type":"object","properties":{"day":{"type":"integer","description":"Day of week (1=Sunday, 7=Saturday)"},"hour":{"type":"integer","description":"Hour of day (0-23, UTC)"},"count":{"type":"integer"}}}}}}}}}}},"400":{"description":"Invalid parameters"},"401":{"description":"Unauthorized"}}}},"/api/admin/histories/charts/model-distribution":{"post":{"summary":"Get conversation model usage distribution","description":"Returns the top 30 most-used conversation models within the date range and institution scope. Results are cached.\n","tags":["Admin Histories"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"institution":{"type":"string","description":"Space-separated institution IDs to filter"},"account":{"type":"string","description":"Space-separated account IDs to filter"},"daterange":{"type":"array","items":{"type":"string","format":"date"},"minItems":2,"maxItems":2,"description":"[startDate, endDate] — defaults to last month if omitted"}}}}}},"responses":{"200":{"description":"Successfully retrieved model distribution data","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Model name (or \"Unknown\")"},"count":{"type":"integer","description":"Number of conversations using this model"}}}}}}}}},"400":{"description":"Invalid parameters"},"401":{"description":"Unauthorized"}}}},"/api/admin/histories/charts/heatmap":{"post":{"summary":"Get peak usage heatmap for conversations","description":"Returns conversation counts grouped by day-of-week and hour-of-day. Useful for identifying peak usage patterns. Results are cached.\n","tags":["Admin Histories"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"institution":{"type":"string","description":"Space-separated institution IDs to filter"},"account":{"type":"string","description":"Space-separated account IDs to filter"},"daterange":{"type":"array","items":{"type":"string","format":"date"},"minItems":2,"maxItems":2,"description":"[startDate, endDate] — defaults to last month if omitted"}}}}}},"responses":{"200":{"description":"Successfully retrieved heatmap data","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"day":{"type":"integer","description":"Day of week (1=Sunday, 7=Saturday)"},"hour":{"type":"integer","description":"Hour of day (0-23, UTC)"},"count":{"type":"integer","description":"Number of conversations in this time slot"}}}}}}}}},"400":{"description":"Invalid parameters"},"401":{"description":"Unauthorized"}}}},"/api/admin/accounts":{"post":{"summary":"Search and retrieve accounts","description":"Retrieves a paginated list of accounts with institution usage statistics","security":[{"apiKeyAuth":[]}],"tags":["Admin Accounts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountSearchRequest"}}}},"responses":{"200":{"description":"Successfully retrieved account list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountListResponse"}}}},"401":{"description":"Unauthorized"}}}},"/api/admin/sessions":{"post":{"summary":"Search and retrieve active sessions","description":"Retrieves a paginated list of user sessions based on search criteria","security":[{"apiKeyAuth":[]}],"tags":["Admin Sessions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionSearchRequest"}}}},"responses":{"200":{"description":"Successfully retrieved session list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionListResponse"}}}},"401":{"description":"Unauthorized"}}}},"/api/admin/feedbacks":{"post":{"summary":"Search and retrieve feedbacks","description":"Retrieves a paginated list of user feedbacks based on search criteria","security":[{"apiKeyAuth":[]}],"tags":["Admin Feedbacks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackSearchRequest"}}}},"responses":{"200":{"description":"Successfully retrieved feedback list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackListResponse"}}}},"401":{"description":"Unauthorized"}}}},"/api/admin/stripePayments":{"post":{"summary":"List payments","description":"Retrieves a paginated list of Stripe payments with optional filtering by account, institution, or user search","tags":["Admin Payments"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentSearchRequest"}}}},"responses":{"200":{"description":"Successfully retrieved payments list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentListResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing access token"},"403":{"description":"Forbidden - Admin access required"}}}},"/api/admin/history/{id}/thinking":{"get":{"summary":"Lazy-fetch the bulk thinking and reasoning array for a single History record (admin)","description":"List endpoints project out the bulk `thinking` text and expose only\n`hasThinking` (boolean) + `thinkingCount` (number of reasoning rounds).\nThis endpoint returns the full thinking array on demand.\nPermission mirrors `getHistoryById`: the History owner sees their own; otherwise\nthe requesting admin must have the `histories.list` entitlement on the History's\ninstitution.\n","tags":["Admin Histories"],"parameters":[{"name":"id","in":"path","required":true,"description":"History record ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Thinking array (may be empty)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"thinking":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"round":{"type":"integer"},"text":{"type":"string"},"signature":{"type":"string"},"model":{"type":"string"},"durationMs":{"type":"integer"}}}}}}}}},"401":{"description":"Authentication required"},"403":{"description":"Forbidden - missing histories.list entitlement"},"404":{"description":"History record not found"}}}},"/api/admin/history/{id}/ragSearch":{"get":{"summary":"Lazy-fetch the structured RAG-KAG retrieval segments for a single History record (admin)","description":"List endpoints project out the bulk `ragSearch` array (multi-KB per row) and expose\nonly `hasRagSearch`, `ragSearchCount`, and `ragSearchMode`. This endpoint returns\nthe full structured retrieval array on demand. Permission mirrors `getHistoryById`:\nowner sees their own; otherwise admin must have `histories.list` on the institution.\n\nConfidential chunks arrive pre-redacted (≤100 chars + suffix when the original\nexceeded the preview cap).\n","tags":["Admin Histories"],"parameters":[{"name":"id","in":"path","required":true,"description":"History record ID","schema":{"type":"string"}}],"responses":{"200":{"description":"ragSearch array (may be empty)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"ragSearch":{"type":"array","items":{"type":"object","properties":{"uploadId":{"type":"string"},"originalname":{"type":"string"},"chunkIndex":{"type":"integer","nullable":true},"score":{"type":"number"},"length":{"type":"integer"},"mode":{"type":"string","enum":["RAG","KAG"]},"chunkText":{"type":"string"},"confidential":{"type":"boolean"}}}}}}}}},"401":{"description":"Authentication required"},"403":{"description":"Forbidden - missing histories.list entitlement"},"404":{"description":"History record not found"}}}},"/api/admin/tool":{"post":{"summary":"Create a new tool","description":"Creates a new tool definition. Super admin only.","tags":["Admin Tools"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolCreateRequest"}}}},"responses":{"201":{"description":"Tool created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolCreateResponse"}}}},"400":{"description":"Invalid properties, duplicate tool name, or save error"},"401":{"description":"Authentication required"},"403":{"description":"Forbidden — super admin only"}}}},"/api/admin/tool/{id}":{"get":{"summary":"Get tool by ID","description":"Retrieves a single tool. Super admin only.","tags":["Admin Tools"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Tool ObjectId"}],"responses":{"200":{"description":"Tool retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolResponse"}}}},"400":{"description":"Invalid tool ID or query error"},"401":{"description":"Authentication required"},"403":{"description":"Forbidden — super admin only"},"404":{"description":"Tool not found"}}},"put":{"summary":"Update tool","description":"Updates an existing tool. Super admin only. Performs a partial update with the\nfields supplied in the body. Note: if the target tool does not exist the handler\nreturns 400 (not 404).\n","tags":["Admin Tools"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Tool ObjectId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolUpdateRequest"}}}},"responses":{"200":{"description":"Tool updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolUpdateResponse"}}}},"400":{"description":"Invalid tool ID, empty body, tool not found, or update error"},"401":{"description":"Authentication required"},"403":{"description":"Forbidden — super admin only"}}},"delete":{"summary":"Delete tool","description":"Soft-deletes a tool by setting `status` to `deleted` and prefixing the name with\na timestamp to free the unique name slot. Super admin only.\n","tags":["Admin Tools"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Tool ObjectId"}],"responses":{"200":{"description":"Tool deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid tool ID or delete error"},"401":{"description":"Authentication required"},"403":{"description":"Forbidden — super admin only"},"404":{"description":"Tool not found"}}}},"/api/admin/tools":{"post":{"summary":"List tools","description":"Returns the tool list. Two modes:\n- **Full mode** (default): paginated, super admin only. Returns full tool objects with\n  `unavailable`/`unavailableReason` enrichment for Google-service tools based on the\n  caller's institution Google Cloud configuration.\n- **Minimum mode** (`minimum=true`): no pagination, available to any authenticated user.\n  Returns a lightweight projection (`name, _id, status, description, rtEnabled, rtOnly, categories`)\n  used by RT dropdown selectors. Excludes both `deleted` and `inactive` tools. Pass\n  `institution` to additionally get Google-service availability flags\n  (`unavailable`/`unavailableReason`/`locked`) computed for that institution.\n","tags":["Admin Tools"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolListRequest"}}}},"responses":{"200":{"description":"Tool list retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToolListResponse"}}}},"400":{"description":"Query error"},"401":{"description":"Authentication required"},"403":{"description":"Forbidden — super admin required for full (non-minimum) mode"}}}},"/api/admin/upload/{id}":{"get":{"summary":"Get upload by ID","description":"Retrieves a single upload by Mongo ObjectId with user and institution populated. The thumbnail Buffer is returned base64-encoded, and if the embeddings DB connection is ready an embeddings array of chunk text (sorted by chunkIndex) is attached.","tags":["Admin Uploads"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Upload ID (Mongo ObjectId)"}],"responses":{"200":{"description":"Upload retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResponse"}}}},"400":{"description":"Bad request — missing or malformed upload id, or an unexpected error was thrown","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Invalid upload Id"}}}}}},"404":{"description":"Upload not found","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Upload not found"}}}}}}}}},"/api/admin/usage-limits":{"get":{"tags":["Admin Usage Limits"],"summary":"Usage-vs-cap reporting (per-user) or account-wide at-limit counts.","description":"Two modes selected by query parameters:\n- `userId` + `institutionId` (or `instanceId` alias): one user's\n  usage-vs-cap gauges plus over-time `{buckets, totals}` (same shape as\n  the user usage chart).\n- `accountId`: account-wide counts of users/memberships at or over a cap.\n","security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"userId","schema":{"type":"string"},"description":"Target user id (per-user mode; requires institutionId/instanceId)."},{"in":"query","name":"institutionId","schema":{"type":"string"},"description":"Institution id for the per-user mode."},{"in":"query","name":"instanceId","schema":{"type":"string"},"description":"Alias for institutionId (forwarded by the usage chart)."},{"in":"query","name":"window","schema":{"type":"integer","default":30},"description":"Over-time window in days (per-user mode)."},{"in":"query","name":"accountId","schema":{"type":"string"},"description":"Account id (account-wide at-limit mode)."}],"responses":{"200":{"description":"Usage-vs-cap or at-limit summary.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","description":"Per-user mode response.","properties":{"success":{"type":"boolean"},"capsEnabled":{"type":"boolean"},"accountWide":{"type":"object","properties":{"used":{"type":"number"},"cap":{"type":"number","nullable":true},"over":{"type":"boolean"}}},"perInstitution":{"type":"object","properties":{"used":{"type":"number"},"cap":{"type":"number","nullable":true},"over":{"type":"boolean"}}},"h24":{"type":"object","properties":{"used":{"type":"number"},"cap":{"type":"number","nullable":true},"over":{"type":"boolean"}}},"buckets":{"type":"array","items":{"type":"object","properties":{"start":{"type":"string"},"credits":{"type":"number"},"messages":{"type":"number"}}}},"totals":{"type":"object","properties":{"credits":{"type":"number"},"messages":{"type":"number"}}}}},{"type":"object","description":"Account-wide at-limit mode response.","properties":{"success":{"type":"boolean"},"capsEnabled":{"type":"boolean"},"atLimit":{"type":"object","properties":{"accountWide":{"type":"integer"},"perInstitution":{"type":"integer"}}},"userCount":{"type":"integer"}}}]}}}},"400":{"description":"Missing required query parameters."},"500":{"description":"Server error."}}}},"/api/admin/users/merge":{"post":{"summary":"Merge a source user into a target user","description":"Admin/super operation. Repoints every ref:'user' across both connections (main + embeddings), unions user_institution memberships (higher role, unioned entitlements/favorites, summed credits), revokes the source's auth/session/credential artifacts, hard-deletes the source, and writes a userMergeAuditLog. Authorization is dual-RAP (source users.delete + target users.edit). A dryRun returns the preview; applying requires confirm:true.\n","tags":["Admin Users"],"security":[{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserMergeRequest"}}}},"responses":{"200":{"description":"Preview (dryRun) or merge result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserMergeResponse"}}}},"400":{"description":"Missing ids, or an apply without confirm:true"},"403":{"description":"Authorization denied (RAP) or source is a super-admin"},"404":{"description":"Source or target user not found"}}}},"/api/admin/users/{userId}/trusted-devices":{"get":{"summary":"List a target user's active MFA-trusted devices (admin)","description":"Same shape as the user-self endpoint, but admin-gated: the caller\nmust hold `users.edit` against the target's institution (super\nadmins bypass). Listing is useful before deciding to revoke an\nindividual device for the user.\n","tags":["Admin"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of active trusted devices for the target user.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"devices":{"type":"array","items":{"$ref":"#/components/schemas/TrustedDevice"}}}}}}},"403":{"description":"Caller lacks users.edit on target's institution."}}},"delete":{"summary":"Revoke ALL trusted devices for a target user (admin)","description":"Revokes every active trusted-device row for the target. Audit log\nrecords the admin's id and `actorType: 'admin'` (or 'super'). Does\nNOT invalidate the target's existing JWT — only forces fresh MFA\non their next login (design Q9 grandfather rule).\n","tags":["Admin"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Devices revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"revoked":{"type":"integer"}}}}}},"403":{"description":"Caller lacks users.edit on target's institution."}}}},"/api/admin/users/{userId}/mfa-audit":{"get":{"summary":"Read the MFA audit log for a target user (super-only)","description":"Returns up to `limit` (default 50, max 200) MFA audit events for the\ntarget user, ordered by `created` descending. Use the `before`\nparameter (ISO timestamp) for pagination — pass the oldest `created`\nfrom the previous page to fetch the next.\n","tags":["Admin"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"userId","required":true,"schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"integer","default":50,"minimum":1,"maximum":200}},{"in":"query","name":"before","description":"ISO-8601 timestamp; results with `created < before` are returned.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Page of audit events.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"events":{"type":"array","items":{"$ref":"#/components/schemas/MfaAuditEvent"}}}}}}},"403":{"description":"Super admin required."}}}},"/api/admin/security/threats":{"get":{"summary":"List Praxis Shield threat incidents for the caller's managed institutions","description":"Returns Security-Watcher incidents scoped to the institutions the caller administers (super sees all). Mounted under the admin gate (isAdmin) and additionally scoped per-institution inside the handler via the `institutions.list` entitlement (super bypasses) — so a Digital Twin admin (global accountType 'admin') sees only the institutions they manage. Heavy evidence/LLM payloads are omitted; returned text is attacker- influenced and MUST be rendered inert by the client.\n","tags":["Admin","Security"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"query","name":"accountId","schema":{"type":"string"},"description":"Filter incidents to the institutions belonging to this account. For non-super callers the account's institutions are intersected with the caller's managed (RAP-scoped) set — no overlap returns 403. For super callers the account's institutions are used directly (no overlap → empty result, not 403).\n"},{"in":"query","name":"institutionId","schema":{"type":"string"},"description":"Narrow to one institution (must be one the caller manages, else 403). When combined with `accountId`, the institution must belong to that account (else 403).\n"},{"in":"query","name":"status","schema":{"type":"string","enum":["all","open","reviewing","resolved","false_positive","escalated"]},"description":"Filter by incident status. `all` shows everything (incl. terminal statuses); a specific status matches exactly; omitted hides terminal statuses (resolved, false_positive) by default.\n"},{"in":"query","name":"severityMin","schema":{"type":"integer","minimum":0,"maximum":4},"description":"Minimum severity (0-4)"},{"in":"query","name":"page","schema":{"type":"integer","default":0}},{"in":"query","name":"limit","schema":{"type":"integer","default":50,"minimum":1,"maximum":200}}],"responses":{"200":{"description":"Paginated incident list","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer"},"incidents":{"type":"array","items":{"$ref":"#/components/schemas/ThreatIncidentSummary"}}}}}}},"400":{"description":"Invalid query parameter (accountId, institutionId, status)"},"403":{"description":"Not authorized to view threats (or for the requested account/institution)"},"500":{"description":"Server error"}}}},"/api/admin/security/threats/{incidentId}":{"get":{"summary":"Get a full Praxis Shield incident (with evidence + LLM assessments)","description":"Returns the full incident document — unlike the list endpoint this does NOT strip `evidence`/`llmAssessments` (the client needs them to render the detail modal). Mounted under the admin gate (isAdmin) and RAP-scoped via the `institutions.list` entitlement (super bypasses): the caller must manage at least one of the incident's institutions. Evidence/LLM text is attacker-influenced and MUST be rendered inert by the client.\n","tags":["Admin","Security"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"incidentId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The full incident","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"incident":{"$ref":"#/components/schemas/ThreatIncidentFull"}}}}}},"400":{"description":"Invalid incidentId"},"403":{"description":"Not authorized for any of the incident institutions"},"404":{"description":"Incident not found"},"500":{"description":"Server error"}}},"patch":{"summary":"Update a Praxis Shield incident (status / severity / categories)","description":"Updates the incident's triage fields. Mounted under the admin gate (isAdmin) and RAP-scoped via the `institutions.edit` entitlement (super bypasses): the caller must manage at least one of the incident's institutions. The change is audited (a `patch_incident` reviewerAction is appended).\n","tags":["Admin","Security"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"incidentId","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["open","reviewing","resolved","false_positive","escalated"],"description":"New incident status (must be a valid incident status)"},"severity":{"type":"integer","minimum":0,"maximum":4,"description":"New severity (clamped 0-4)"},"categories":{"type":"array","items":{"type":"string"},"description":"Replacement category list (non-string entries dropped)"}}}}}},"responses":{"200":{"description":"The updated incident","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"incident":{"$ref":"#/components/schemas/ThreatIncidentFull"}}}}}},"400":{"description":"Invalid incidentId, invalid status, or no valid fields to update"},"403":{"description":"Not authorized for any of the incident institutions"},"404":{"description":"Incident not found"},"500":{"description":"Server error"}}}},"/api/admin/security/threats/{incidentId}/history":{"get":{"summary":"Get the live flagged history records cited by a Praxis Shield incident","description":"Returns the live history record(s) referenced by the incident's evidence (deduplicated). Mounted under the admin gate (isAdmin) and RAP-scoped via the `institutions.list` entitlement (super bypasses): the caller must manage at least one of the incident's institutions. Only inert-safe fields are projected (never secrets, embeddings, or full payloads). The `input`/`output`/`tools` text is attacker-influenced and MUST be rendered INERT by the client.\n","tags":["Admin","Security"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"incidentId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The cited history records (possibly empty)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"incidentId":{"type":"string"},"histories":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string"},"input":{"type":"string","description":"Attacker-influenced; render inert"},"output":{"type":"string","description":"Attacker-influenced; render inert"},"tools":{"description":"Tool invocations recorded on the turn (inert)"},"status":{"type":"string"},"created":{"type":"string","format":"date-time"},"type":{"type":"string"},"userEmail":{"type":"string"},"user":{"type":"string"}}}}}}}}},"400":{"description":"Invalid incidentId"},"403":{"description":"Not authorized for any of the incident institutions"},"404":{"description":"Incident not found"},"500":{"description":"Server error"}}}},"/api/admin/security/threats/{incidentId}/notes":{"post":{"summary":"Add a reviewer note to a Praxis Shield incident","description":"Appends a reviewer note to the incident. Mounted under the admin gate (isAdmin) and RAP-scoped via the `institutions.edit` entitlement (super bypasses): the caller must manage at least one of the incident's institutions.\n","tags":["Admin","Security"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"incidentId","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"type":"string","description":"Reviewer note text (trimmed; required non-empty)"}}}}}},"responses":{"200":{"description":"The updated incident","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"incident":{"$ref":"#/components/schemas/ThreatIncidentFull"}}}}}},"400":{"description":"Invalid incidentId or missing note"},"403":{"description":"Not authorized for any of the incident institutions"},"404":{"description":"Incident not found"},"500":{"description":"Server error"}}}},"/api/admin/security/threats/{incidentId}/actions":{"post":{"summary":"Record a reviewer action intent on a Praxis Shield incident","description":"Appends an INTENT-ONLY reviewer action to the incident (recorded for audit but never executed by the institution panel — `metadata.intentOnly` is forced true). Mounted under the admin gate (isAdmin) and RAP-scoped via the `institutions.edit` entitlement (super bypasses): the caller must manage at least one of the incident's institutions.\n","tags":["Admin","Security"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"incidentId","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","description":"Action identifier (trimmed; required non-empty)"},"metadata":{"type":"object","description":"Optional structured metadata (intentOnly forced true)"}}}}}},"responses":{"200":{"description":"The updated incident","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"incident":{"$ref":"#/components/schemas/ThreatIncidentFull"}}}}}},"400":{"description":"Invalid incidentId or missing action"},"403":{"description":"Not authorized for any of the incident institutions"},"404":{"description":"Incident not found"},"500":{"description":"Server error"}}}},"/api/admin/security/threats/{incidentId}/suspend-user":{"post":{"summary":"Suspend the user flagged by a Praxis Shield incident","description":"Remediation action. The `scope` body field selects the axis (defaults to `institution`; if omitted, super defaults to `global`). `global` is super-only — it deactivates the account (user.status='inactive'), sets all memberships inactive, and revokes the user's keys/sessions. `institution` sets ONLY the userInstitution memberships tying the user to the incident institutions the caller administers inactive; the account stays active elsewhere and no tokens are revoked. Mounted under the admin gate (isAdmin) and scoped per-institution via the `institutions.edit` entitlement (super bypasses). Cannot suspend a super-admin or yourself.\n","tags":["Admin","Security"],"security":[{"apiKeyAuth":[]}],"parameters":[{"in":"path","name":"incidentId","required":true,"schema":{"type":"string"},"description":"Incident id (drives the target user and the institution scope)"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"scope":{"type":"string","enum":["institution","global"],"description":"Suspend axis. `institution` (default) inactivates only the memberships for the incident institutions the caller manages. `global` (super-only) deactivates the account + revokes keys/sessions.\n"}}}}}},"responses":{"200":{"description":"Suspend applied","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"scope":{"type":"string","enum":["global","institution"]},"suspendedMemberships":{"type":"integer"},"userDeactivated":{"type":"boolean"},"tokensRevoked":{"type":"boolean"}}}}}},"400":{"description":"Invalid incidentId / incident has no user"},"403":{"description":"Not authorized, target is super-admin, self-suspend, or `global` scope requested by a non-super caller\n"},"404":{"description":"Incident or target user not found"},"500":{"description":"Server error"}}}}},"components":{"securitySchemes":{"apiKeyAuth":{"type":"apiKey","in":"header","name":"x-access-token","description":"JWT token passed in x-access-token header"},"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT token passed in authorization header"},"priaApiKey":{"type":"apiKey","in":"header","name":"x-api-key","description":"Long-lived Pria API key (format `pria_<40 hex chars>`) used to obtain a JWT via\n`POST /api/auth/api-key-signin`. Only valid on the api-key-signin endpoint —\nevery other admin/user endpoint expects the JWT issued by that exchange (sent as\n`Authorization: Bearer <jwt>` or `x-access-token: <jwt>`).\n"}},"schemas":{"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","description":"Human-readable error message","example":"Invalid User Institution"},"error":{"type":"string","description":"Technical error details"}}},"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","description":"Success message","example":"User institution deleted successfully"}}},"GPSCoordinates":{"type":"object","description":"Geographic location data from device","properties":{"accuracy":{"type":"number","description":"GPS accuracy in meters","example":21126.84},"latitude":{"type":"number","description":"Latitude coordinate (-90 to 90)","example":-21.282816},"longitude":{"type":"number","description":"Longitude coordinate (-180 to 180)","example":55.4139648},"altitude":{"type":"number","nullable":true,"description":"Altitude in meters above sea level"},"altitudeAccuracy":{"type":"number","nullable":true,"description":"Altitude accuracy in meters"},"heading":{"type":"number","nullable":true,"description":"Direction of travel in degrees (0-360)"},"speed":{"type":"number","nullable":true,"description":"Speed in meters per second"}}},"AssistantReference":{"type":"object","description":"Reference to an AI assistant for conversation context","properties":{"_id":{"type":"string","description":"Assistant unique identifier (MongoDB ObjectId)","example":"6856fa89cbafcff8d98680f5"},"name":{"type":"string","description":"Assistant display name","example":"Research Assistant"}}},"ConversationContext":{"type":"object","description":"Context for a conversation session (course/topic)","properties":{"course_id":{"type":"number","description":"Unique conversation/course identifier (epoch timestamp)","example":1750532703472},"course_name":{"type":"string","description":"Display name for the conversation","example":"Research Project Discussion"},"assistant":{"$ref":"#/components/schemas/AssistantReference"},"history_count":{"type":"integer","description":"Number of dialogue entries in this conversation","example":15},"last_dialogue_date":{"type":"string","format":"date-time","description":"Timestamp of most recent message"}}},"InstitutionSummary":{"type":"object","description":"Basic institution information for references","properties":{"_id":{"type":"string","description":"Institution unique identifier","example":"6631915765bb0a94cfd6ca99"},"name":{"type":"string","description":"Institution domain identifier","example":"myschool.instructure.com"},"ainame":{"type":"string","description":"AI assistant display name","example":"Hugo"},"picture":{"type":"string","format":"uri","description":"Institution avatar/logo URL"},"status":{"type":"string","enum":["active","inactive"],"description":"Institution status"}}},"UserSummary":{"type":"object","description":"Basic user information for references","properties":{"_id":{"type":"string","description":"User unique identifier"},"email":{"type":"string","format":"email","description":"User email address"},"fname":{"type":"string","description":"First name"},"lname":{"type":"string","description":"Last name"},"accountType":{"type":"string","enum":["user","admin","super"],"description":"User account privilege level"}}},"PaginationMeta":{"type":"object","description":"Pagination metadata for list responses","properties":{"total":{"type":"integer","description":"Total number of records"},"limit":{"type":"integer","description":"Maximum records per page"},"offset":{"type":"integer","description":"Number of records skipped"},"hasMore":{"type":"boolean","description":"Whether more records are available"}}},"TokenUsage":{"type":"object","description":"AI token consumption metrics","properties":{"usage":{"type":"integer","description":"Total tokens consumed","example":1234},"cached":{"type":"integer","description":"Tokens served from cache","example":500},"completion":{"type":"integer","description":"Tokens used for response generation","example":734}}},"CreditInfo":{"type":"object","description":"Credit consumption and balance information","properties":{"credits":{"type":"number","description":"Credits consumed for this request"},"creditsUsed":{"type":"number","description":"Total credits used by user/institution"},"totalCredits":{"type":"number","description":"Total credits available"}}},"DatabaseStatusResponse":{"type":"object","properties":{"message":{"type":"string","description":"Description of database connection states","example":"0: disconnected, 1: connected, 2: connecting, 3: disconnecting, 4: invalid credentials"},"status":{"type":"integer","description":"Current database connection status (0-4)","example":1,"enum":[0,1,2,3,4]}}},"MemoryInfo":{"type":"object","properties":{"rss":{"type":"number","description":"Resident Set Size - total memory allocated for the process"},"heapTotal":{"type":"number","description":"Total heap memory allocated"},"heapUsed":{"type":"number","description":"Heap memory currently in use"},"external":{"type":"number","description":"Memory used by C++ objects bound to JavaScript objects"},"arrayBuffers":{"type":"number","description":"Memory allocated for ArrayBuffers and SharedArrayBuffers"}}},"Dependencies":{"type":"object","properties":{"database":{"type":"number","description":"Database connection status (1 = connected)"}}},"EventLoopLag":{"type":"object","nullable":true,"description":"Event loop lag percentiles in ms over the current sampling window. Null until the monitor's histogram has data.","properties":{"meanMs":{"type":"number","description":"Mean event loop lag (ms)"},"p50Ms":{"type":"number","description":"P50 event loop lag (ms)"},"p95Ms":{"type":"number","description":"P95 event loop lag (ms)"},"p99Ms":{"type":"number","description":"P99 event loop lag (ms)"},"maxMs":{"type":"number","description":"Max event loop lag (ms) since last reset"}}},"MongoInfo":{"type":"object","properties":{"state":{"type":"number","description":"0=disconnected, 1=connected, 2=connecting, 3=disconnecting"},"label":{"type":"string","example":"connected"}}},"HealthResponse":{"type":"object","properties":{"status":{"type":"string","description":"Overall health status","example":"ok"},"uptime":{"type":"number","description":"Server uptime in seconds"},"timestamp":{"type":"number","description":"Current timestamp in milliseconds"},"summary":{"type":"string","description":"Human-readable one-line digest matching the heartbeat log format","example":"Event loop lag: 1.9s | RSS: 1.7GB | heap: 506MB/1.1GB | mongo: connected"},"eventLoopLag":{"$ref":"#/components/schemas/EventLoopLag"},"mongo":{"$ref":"#/components/schemas/MongoInfo"},"memory":{"$ref":"#/components/schemas/MemoryInfo"},"dependencies":{"$ref":"#/components/schemas/Dependencies"}}},"TestUrlRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"The LTI context URL to be tested. Used to find an institution by matching against registered LTI context IDs.","example":"https://domain.edu/7891273"},"institutionid":{"type":"string","format":"uuid","description":"The public ID of the institution to lookup directly. Used as fallback if URL lookup fails.","example":"bc6efd03-9d01-43e7-bd49-c4af1c54ae3a"},"email":{"type":"string","format":"email","description":"The email address of the user to check membership status for.","example":"user@domain.edu"},"userid":{"type":"string","description":"The LXP user ID to lookup the user by (alternative to email).","example":"lxp_12345"}}},"TestUrlResponse":{"type":"object","properties":{"ltiassigned":{"type":"boolean","description":"Indicates if the institution was found via LTI context URL match","example":true},"name":{"type":"string","description":"Name of the institution","example":"Acme University"},"status":{"type":"string","description":"Status of the institution, user, or user-institution membership. Possible values include 'active', 'inactive', 'pending', 'suspended', 'invalid'.","example":"active","enum":["active","inactive","pending","suspended","invalid","deleted"]},"ainame":{"type":"string","description":"AI/Digital Twin name associated with the institution","example":"Hugo"},"picture":{"type":"string","description":"Picture URL for the institution branding","example":"https://storage.example.com/logo.png"},"publicId":{"type":"string","format":"uuid","description":"Public identifier for the institution","example":"bc6efd03-9d01-43e7-bd49-c4af1c54ae3a"}}},"SignupRequest":{"type":"object","required":["email","password","fname"],"properties":{"email":{"type":"string","format":"email","description":"User email address","example":"john.doe@domain.com"},"password":{"type":"string","format":"password","minLength":6,"description":"User password (minimum 6 characters)","example":"mySecurePassword123"},"fname":{"type":"string","description":"First name","example":"John"},"lname":{"type":"string","description":"Last name","example":"Doe"},"picture":{"type":"string","format":"uri","description":"Profile picture URL"}}},"SignupResponse":{"type":"object","properties":{"token":{"type":"string","description":"JWT authentication token"},"profile":{"$ref":"#/components/schemas/UserProfile"}}},"GenerateResetCodeRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"Email address of the user","example":"john.doe@domain.com"}}},"GenerateResetCodeResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Reset code sent to email"}}},"CheckResetCodeRequest":{"type":"object","required":["email","resetCode"],"properties":{"email":{"type":"string","format":"email","description":"User email address","example":"john.doe@domain.com"},"resetCode":{"type":"string","description":"Reset code received via email","example":"ABC123"}}},"CheckResetCodeResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"valid":{"type":"boolean","description":"Whether the reset code is valid","example":true}}},"ChangePasswordRequest":{"type":"object","required":["email","resetCode","password"],"properties":{"email":{"type":"string","format":"email","description":"User email address","example":"john.doe@domain.com"},"resetCode":{"type":"string","description":"Valid reset code","example":"ABC123"},"password":{"type":"string","format":"password","minLength":6,"description":"New password (minimum 6 characters)","example":"newSecurePassword456"}}},"ChangePasswordResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Password changed successfully"}}},"CheckActivateCodeRequest":{"type":"object","required":["email","activateCode"],"properties":{"email":{"type":"string","format":"email","description":"User email address"},"activateCode":{"type":"string","description":"Account activation code"}}},"CheckActivateCodeResponse":{"type":"object","properties":{"success":{"type":"boolean"},"valid":{"type":"boolean","description":"Whether the activation code is valid"}}},"GetInstitutionsForContextRequest":{"type":"object","required":["ltiContextId"],"properties":{"ltiContextId":{"type":"string","description":"LTI context identifier","example":"https://domain.edu/course/123"}}},"GetInstitutionsForContextResponse":{"type":"object","properties":{"success":{"type":"boolean"},"institutions":{"type":"array","items":{"$ref":"#/components/schemas/InstitutionProfile"}}}},"GoogleOAuthValidateResponse":{"type":"object","properties":{"valid":{"type":"boolean","description":"Whether the stored Google access_token is currently accepted by Google.","example":true},"source":{"type":"string","nullable":true,"enum":["institution","user"],"description":"Which storage location the validated token came from. `institution` means it\nlived on the user's `UserInstitution.googleLoginToken`; `user` means the\npersonal token on the User record. Null when no token is configured.\n"},"cleared":{"type":"boolean","description":"Set to true when an invalid token was just cleared from the database.","example":false},"message":{"type":"string","description":"Set when no token is configured for the resolved source."},"error":{"type":"string","description":"Set on validation failure — passes through Google's error message."}}},"GoogleOAuthRevokeResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Google services access revoked"}}},"SignInRequest":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email","example":"john.doe@mydomain.com"},"password":{"type":"string","format":"password","example":"iLovePria123"}}},"SignInResponse":{"type":"object","description":"Successful signin response shape. Two variants are returned by the\nsame endpoint depending on whether email MFA is required:\n  • **JWT issued** — `{ token, profile }`. The user is signed in.\n  • **MFA challenge** — `{ mfaRequired: true, challengeId, maskedEmail, mandatorySuper? }`.\n    The client must POST the 6-digit code to `/api/auth/mfa/verify`\n    with the challengeId; the verify endpoint then issues the JWT.\n  Discriminate via `mfaRequired === true` (per Phase 1 design §6.1).\n","properties":{"token":{"type":"string","description":"Signed JWT token. Present when MFA is not required or has just been verified. Include this in subsequent API requests via the x-access-token header or Authorization Bearer header. Expires after 6 hours (configurable via JWT_VALIDITY_SEC). Automatically refreshed on profile load (sliding session).","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NDMwNzM2ZmQ2MmQ2NTAwNDA0MjA2NzQiLCJlbWFpbCI6ImpvaG4uZG9lQG15ZG9tYWluLmNvbSIsImN1c3RvbWVySWQiOiJjdXNfTnh4eHh4eCIsImFjY291bnRUeXBlIjoidXNlciIsInNlc3Npb25JZCI6InMlM0FhYmMxMjMiLCJpYXQiOjE3MDAwMDAwMDAsImV4cCI6MTcwMDA4NjQwMH0.signature"},"profile":{"$ref":"#/components/schemas/UserProfile"},"mfaRequired":{"type":"boolean","description":"When true, the response is an MFA challenge — no JWT issued. Client should redirect to the MFA verify screen with the challengeId.","example":true},"challengeId":{"type":"string","description":"MongoDB ObjectId of the issued mfaChallenge. Only present when `mfaRequired: true`. POST this to `/api/auth/mfa/verify` alongside the 6-digit code.\n","example":"6856fa89cbafcff8d98680f5"},"maskedEmail":{"type":"string","description":"Partially-masked email address the verification code was sent to (for the verify-screen \"code sent to …\" prompt). Only present when `mfaRequired: true`.\n","example":"j*****e@example.com"},"mandatorySuper":{"type":"boolean","description":"Phase 2 — when `true`, this MFA challenge was issued under\nsuper-mandatory enforcement (MFA_SUPER_MANDATORY=true and the\nuser is past the rollout date). The verify screen should\nrender an explanatory banner and suppress the Cancel\naffordance, since the user can't dismiss the flow without\nenrolling. On successful verify, the server persists\n`user.mfaEnabled = true` so the next signin follows the\nnormal phase-1 trusted-device path.\n\nOnly present when `mfaRequired: true` AND the gate fired.\nOmitted (not `false`) otherwise — clients should default to\n`false` when absent.\n","example":true}}},"AutoSignupRequest":{"type":"object","required":["email","fname","lxp_user_id","lxp_user_type","lxp_partner_id","lxp_partner_name","lxp_role_id","lxp_role_name","institution_id","institution_url","client_ip","lticontextid","digitaltwin"],"properties":{"email":{"type":"string","format":"email","example":"john.doe@praxis-ai.com"},"fname":{"type":"string","example":"John"},"lname":{"type":"string","example":"Doe"},"picture":{"type":"string","example":""},"lxp_user_id":{"type":"string","example":"1750665682"},"lxp_user_type":{"type":"integer","example":1},"lxp_partner_id":{"type":"string","example":"1"},"lxp_partner_name":{"type":"string","example":"Edu School"},"lxp_role_id":{"type":"integer","example":123},"lxp_role_name":{"type":"string","example":"Course 123"},"institution_id":{"type":"string","format":"uuid","example":"bc6efd03-9d01-43e7-bd49-c4af1c54ae3a"},"institution_url":{"type":"string","example":"https://domain.edu"},"client_ip":{"type":"string","example":"134.123.234.234"},"lticontextid":{"type":"string","example":"https://domain.edu/course/i12"},"digitaltwin":{"type":"boolean","example":true}}},"InstitutionProfile":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"picture":{"type":"string"},"picture_bg":{"type":"string"},"picture_dark_bg":{"type":"string"},"picture_animated":{"type":"string"},"elevenlabs_agent_id":{"type":"string"},"credits":{"type":"integer"},"status":{"type":"string"},"allowJoining":{"type":"string"},"joiningAdminOnly":{"type":"boolean"},"publicId":{"type":"string","format":"uuid"},"publicAuthorizedUrls":{"type":"array","items":{"type":"string"}},"ainame":{"type":"string"},"contactEmail":{"type":"string","format":"email"},"creditAward":{"type":"integer"},"poolCredits":{"type":"boolean"},"invoices_urls":{"type":"array","items":{"type":"string"}},"maxCompletionTokens":{"type":"integer"},"disableFileUploadForUser":{"type":"boolean"},"disableAudioNotesForUser":{"type":"boolean"},"toolsDisabled":{"type":"array","items":{"type":"string"}},"ltiContextIds":{"type":"array","items":{"type":"string"}},"personalisationAsked":{"type":"boolean"},"locationEnabled":{"type":"boolean"},"rtEnabled":{"type":"boolean"},"rtAdminOnly":{"type":"boolean"},"displayAgentDetails":{"type":"boolean"},"displayThinkingDetails":{"type":"boolean"},"displayRagSearchDetails":{"type":"boolean"},"displayThinkingExecution":{"type":"boolean"},"displayToolExecution":{"type":"boolean"},"assistantsDisabled":{"type":"array","items":{"type":"string"}},"disableAssistantsForUser":{"type":"boolean"},"rtVoice":{"type":"string"},"maxFiles":{"type":"integer"},"questionType":{"type":"string"},"creditsTotal":{"type":"integer","nullable":true},"creditsUsagePct":{"type":"number"},"id":{"type":"string"}}},"UserProfile":{"type":"object","properties":{"_id":{"type":"string"},"email":{"type":"string","format":"email"},"fname":{"type":"string"},"lname":{"type":"string"},"picture":{"type":"string"},"accountType":{"type":"string"},"permissions":{"type":"array","items":{"type":"string"}},"customerId":{"type":"string"},"lxp_user_id":{"type":"string"},"lxp_user_type":{"type":"integer"},"lxp_partner_id":{"type":"string"},"lxp_partner_name":{"type":"string"},"lxp_role_id":{"type":"integer"},"lxp_role_name":{"type":"string"},"credits":{"type":"integer"},"creditsUsed":{"type":"integer"},"plan":{"type":"string"},"status":{"type":"string"},"trial_end":{"type":"string","format":"date-time"},"trial_used":{"type":"boolean"},"current_period_end":{"type":"string","format":"date-time"},"cancel_at_period_end":{"type":"boolean"},"referralId":{"type":"string","format":"uuid"},"referrerPaid":{"type":"boolean"},"resetCodeId":{"type":"string","format":"uuid"},"invoices_urls":{"type":"array","items":{"type":"string"}},"remember_history_count":{"type":"integer"},"browser_voice":{"type":"string"},"rt_voice":{"type":"string"},"use_location":{"type":"boolean"},"showSideBar":{"type":"boolean"},"dark_mode":{"type":"boolean"},"created":{"type":"string","format":"date-time"},"__v":{"type":"integer"},"institution":{"$ref":"#/components/schemas/InstitutionProfile"}}},"AutoSignupResponse":{"type":"object","properties":{"token":{"type":"string","description":"JWT authentication token"},"profile":{"$ref":"#/components/schemas/UserProfile"}}},"CreateInstitutionRequest":{"type":"object","required":["name","contactEmail"],"properties":{"name":{"type":"string","description":"Institution name identifier","example":"teacher1.domain.edu.instructure"},"contactEmail":{"type":"string","format":"email","description":"Contact email for the institution","example":"jane.doe@praxis-ai.com"},"ltiContextIds":{"type":"array","items":{"type":"string"},"description":"Array of LTI context identifiers (defaults to empty array if omitted)","example":["https://domain.edu/7891278"]},"publicAuthorizedUrls":{"type":"array","items":{"type":"string"},"description":"Array of authorized public URLs","example":["https://domain.edu"]},"ainame":{"type":"string","description":"AI assistant name","example":"Hugo"},"picture":{"type":"string","format":"uri","description":"URL to the institution's picture/avatar","example":"https://hiimpria.ai/uploads/6430736fd62d650040420674/Clarkey20Headshot20Animated_1752713782148.gif"},"picture_bg":{"type":"string","description":"Background picture URL","example":""},"picture_dark_bg":{"type":"string","description":"Dark mode background picture URL","example":""},"picture_animated":{"type":"string","format":"uri","description":"URL to animated picture/avatar","example":"https://hiimpria.ai/uploads/6430736fd62d650040420674/Clarkey20Headshot20Animated_1752713782148.gif"},"elevenlabs_agent_id":{"type":"string","description":"ElevenLabs Agent ID for Digital Twin Voice","example":"agent_xxxxxxxxxxxxxxxxxxxx"},"prompt":{"type":"string","description":"Custom prompt/instructions for the AI persona","example":""},"domain":{"type":"string","description":"Institution domain, used for account lookup","example":"domain.edu"},"account":{"type":"string","description":"Parent account ObjectId (if known)","example":"6430736fd62d650040420674"},"allowJoining":{"type":"string","enum":["disabled","account","public"],"description":"Who can join this Digital Twin","example":"disabled"},"poolCredits":{"type":"boolean","description":"Whether credits are pooled on behalf of users","example":true},"creditAward":{"type":"integer","description":"Credits awarded to new users on registration (when poolCredits is false)","example":0},"questionType":{"type":"string","description":"Type of personalization question bank (CORPORATE for Digital Twin, INSTITUTION for Digital Expert)","example":"CORPORATE"},"rtEnabled":{"type":"boolean","description":"Enable real-time voice conversations","example":false},"rtAdminOnly":{"type":"boolean","description":"Restrict real-time conversations to admins only","example":true}}},"CreateInstitutionResponse":{"type":"object","properties":{"_id":{"type":"string","description":"Unique institution identifier","example":"68793ef2a8a4a5eaff36e7ca"},"name":{"type":"string","example":"teacher1.domain.edu.instructure"},"picture":{"type":"string","format":"uri","example":"https://hiimpria.ai/uploads/6430736fd62d650040420674/Clarkey20Headshot20Animated_1752713782148.gif"},"picture_bg":{"type":"string","example":""},"picture_dark_bg":{"type":"string","example":""},"picture_animated":{"type":"string","format":"uri","example":"https://hiimpria.ai/uploads/6430736fd62d650040420674/Clarkey20Headshot20Animated_1752713782148.gif"},"elevenlabs_agent_id":{"type":"string","description":"ElevenLabs Agent ID for Digital Twin Voice","example":"agent_xxxxxxxxxxxxxxxxxxxx"},"credits":{"type":"number","description":"Total credits allocated","example":50},"status":{"type":"string","enum":["active","inactive"],"example":"active"},"allowJoining":{"type":"string","enum":["disabled","account","public"],"example":"disabled"},"joiningAdminOnly":{"type":"boolean","example":false},"publicId":{"type":"string","format":"uuid","description":"Public identifier for the institution","example":"2e1006ec-5b59-4431-96d2-b0e1b1022a3a"},"publicAuthorizedUrls":{"type":"array","items":{"type":"string"},"example":["https://domain.edu"]},"ainame":{"type":"string","example":"Hugo"},"prompt":{"type":"string","example":""},"contactEmail":{"type":"string","format":"email","example":"jane.doe@praxis-ai.com"},"creditAward":{"type":"number","example":0},"poolCredits":{"type":"boolean","example":true},"questionType":{"type":"string","description":"Personalization question bank type","example":"CORPORATE"},"rtEnabled":{"type":"boolean","description":"Whether real-time voice conversations are enabled","example":false},"rtAdminOnly":{"type":"boolean","description":"Whether real-time conversations are restricted to admins","example":true},"created":{"type":"string","format":"date-time","example":"2025-07-17T18:20:34.330Z"},"id":{"type":"string","example":"68793ef2a8a4a5eaff36e7ca"}}},"GeneratePromptPreviewRequest":{"type":"object","required":["questions"],"properties":{"ainame":{"type":"string","description":"Name of the Digital Twin being created","example":"Dr. Smith"},"questions":{"type":"array","description":"Interview Q&A pairs","items":{"type":"object","required":["question","answer"],"properties":{"code":{"type":"string","description":"Question bank code","example":"EXPERTISE"},"question":{"type":"string","description":"The interview question","example":"What is your area of expertise?"},"objectif":{"type":"string","description":"Purpose of the question"},"answer":{"type":"string","description":"User's answer","example":"Machine learning and neural networks"}}}}}},"GeneratePromptPreviewResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"data":{"type":"string","description":"Generated persona instructions text"}}}}},"SdkSignRequest":{"type":"object","required":["params","institutionId"],"properties":{"params":{"type":"object","description":"Launch parameters to be HMAC-signed. All values are canonicalized (converted to strings\nand `launch_*` keys are stripped) before signing to ensure consistency with the verify side,\nsince URL query strings coerce all values to strings.\n","properties":{"email":{"type":"string","format":"email","example":"john.doe@domain.edu"},"profilename":{"type":"string","example":"John Doe"},"usertype":{"type":"integer","description":"LMS user type (1=student, 2+=instructor/admin)","example":4},"userid":{"type":"integer","example":110},"roleid":{"type":"integer","example":123},"rolename":{"type":"string","example":"Course ABC"},"partnerid":{"type":"integer","example":1},"partnername":{"type":"string","example":"ABC Global Inc."},"lticontextid":{"type":"string","example":"https://domain.edu/course/123"},"digitaltwin":{"type":"boolean","description":"When true with empty institutionId, enters digital twin selector mode","example":true},"task":{"type":"string","example":"do"},"institutionid":{"type":"string","format":"uuid","description":"Institution public ID (may be empty for digital twin mode)","example":"f831501f-b645-481a-9cbb-331509aaf8c1"},"institutionurl":{"type":"string","example":"https://domain.edu"},"clientip":{"type":"string","example":"134.123.234.234"}}},"institutionId":{"type":"string","description":"Institution public UUID. Required for institution-specific launches.\nMay be an empty string `\"\"` for digital twin selector mode (when `params.digitaltwin` is true),\nwhich skips institution lookup and origin validation.\n","example":"f831501f-b645-481a-9cbb-331509aaf8c1"}}},"SdkSignResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"launch_token":{"type":"string","description":"HMAC-SHA256 signature of the canonicalized launch parameters","example":"a1b2c3d4e5f6..."},"nonce":{"type":"string","description":"Cryptographic nonce (32 hex chars) to prevent replay attacks","example":"f47ac10b58cc4372a5670e02b2c3d479"},"timestamp":{"type":"integer","description":"Unix timestamp (seconds) when the token was issued","example":1740500000}}},"SdkVerifyRequest":{"type":"object","required":["params","launch_token","nonce","timestamp"],"properties":{"params":{"type":"object","description":"The launch parameters to verify. May include `launch_token`, `launch_nonce`, and\n`launch_timestamp` keys (these are stripped during canonicalization before HMAC comparison).\nAll values are stringified to match the sign-side canonicalization.\n"},"launch_token":{"type":"string","description":"The HMAC-SHA256 token returned from sdk-sign","example":"a1b2c3d4e5f6..."},"nonce":{"type":"string","description":"The nonce returned from sdk-sign","example":"f47ac10b58cc4372a5670e02b2c3d479"},"timestamp":{"type":"integer","description":"The timestamp returned from sdk-sign (must be within 10-minute window)","example":1740500000}}},"SdkVerifyResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true}}},"ApiKeySigninResponse":{"type":"object","properties":{"token":{"type":"string","description":"JWT signed for the API-key-bound user. Use it in subsequent calls via\n`Authorization: Bearer <token>` or the `x-access-token` header. Token TTL\nmatches the normal signin flow (6 hours by default, configurable via\n`JWT_VALIDITY_SEC`).\n","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."},"profile":{"type":"object","description":"Minimal profile envelope tailored for SDK / integration consumers.\nSmaller than the regular signin profile — see properties below.\n","properties":{"_id":{"type":"string","example":"6430736fd62d650040420674"},"email":{"type":"string","format":"email","example":"integration-bot@praxis-ai.com"},"fname":{"type":"string","example":"Integration"},"lname":{"type":"string","example":"Bot"},"accountType":{"type":"string","enum":["admin","super"],"description":"API-key signin is gated to admin/super accounts only.","example":"admin"},"plan":{"type":"string","example":"pro"},"status":{"type":"string","example":"active"},"credits":{"type":"integer","example":1000},"creditsUsed":{"type":"integer","example":12},"institution":{"type":"object","nullable":true,"description":"Trimmed institution summary (only set when the user belongs to one).","properties":{"_id":{"type":"string","example":"68793ef2a8a4a5eaff36e7ca"},"name":{"type":"string","example":"domain.edu"},"status":{"type":"string","example":"active"},"credits":{"type":"integer","example":500},"ainame":{"type":"string","example":"Hugo"}}}}}}},"MfaVerifyRequest":{"type":"object","required":["challengeId","code"],"properties":{"challengeId":{"type":"string","description":"Opaque server-issued challenge identifier returned by POST /api/auth/signin (or /autosignup) when the response carried mfaRequired=true.","example":"65f2c9a1d8e4b3c5a1234567"},"code":{"type":"string","pattern":"^[0-9]{6}$","description":"The 6-digit numeric code the user received by email.","example":"472918"}}},"MfaChallengeIdRequest":{"type":"object","required":["challengeId"],"properties":{"challengeId":{"type":"string","description":"Opaque server-issued challenge identifier.","example":"65f2c9a1d8e4b3c5a1234567"}}},"MfaResendResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"challengeId":{"type":"string","description":"Identifier of the freshly-issued challenge. The prior challenge is invalidated; future verify / resend / cancel calls must use this new id.","example":"65f2cabe1234e5f6a8765432"},"maskedEmail":{"type":"string","example":"hu***@praxis-ai.com"}}},"MfaVerifyError":{"type":"object","properties":{"success":{"type":"boolean","example":false},"code":{"type":"string","enum":["BAD_REQUEST","WRONG_CODE","CHALLENGE_EXPIRED","TOO_MANY_ATTEMPTS","USER_MISSING","RESEND_COOLDOWN","MFA_NOT_CONFIGURED","MFA_EMAIL_FAILED"]},"message":{"type":"string"},"attemptsRemaining":{"type":"integer","description":"Only present on WRONG_CODE / TOO_MANY_ATTEMPTS responses.","example":4},"retryAfter":{"type":"integer","description":"Seconds the client should wait before retrying. Present on RESEND_COOLDOWN.","example":24}}},"Account":{"type":"object","properties":{"_id":{"type":"string","description":"Account ID"},"name":{"type":"string","description":"Account name"},"managerEmail":{"type":"string","format":"email","description":"Email of the account's primary manager (required)."},"credits":{"type":"integer","description":"Account credits."},"domainUrls":{"type":"array","items":{"type":"string"},"description":"Domains associated with the account."},"status":{"type":"string","enum":["active","inactive","deleted"],"description":"Account status. Soft-delete sets this to `deleted`."},"qualification":{"type":"string","description":"Sales pipeline qualification (Suspect, Prospect, MQL, SQL, …)."},"created":{"type":"string","format":"date-time","description":"Creation timestamp"},"creditCaps":{"type":"object","description":"Per-user credit caps (super-only). Disabled by default.","properties":{"enabled":{"type":"boolean","default":false},"perUserAccountTotal":{"type":"number","nullable":true},"perUserInstitutionDefault":{"type":"number","nullable":true},"perUser24hDefault":{"type":"number","nullable":true}}}}},"AccountCreateRequest":{"type":"object","required":["name","managerEmail"],"properties":{"name":{"type":"string","description":"Account name (lowercase, unique)."},"managerEmail":{"type":"string","format":"email","description":"Email of the primary account manager (required)."},"status":{"type":"string","enum":["active","inactive","deleted"],"default":"active"},"credits":{"type":"integer","description":"Initial credits"}}},"AccountResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Account"}}},"AccountTransfer":{"type":"object","properties":{"_id":{"type":"string","description":"Transfer ID"},"institution":{"type":"string","description":"Institution ObjectId (optional - present for institution-scoped transfers)"},"account":{"type":"string","description":"Account ObjectId (optional - present for account-scoped transfers and zero-sum institution transfers)"},"user":{"type":"string","description":"User ObjectId of the admin who made the change"},"credits":{"type":"number","description":"Credit delta (can be negative)"},"status":{"type":"string","description":"active, inactive, or deleted","example":"active"},"created":{"type":"string","format":"date-time","description":"Transfer timestamp"},"transferMode":{"type":"integer","description":"1 = manual by admin, 2 = auto, 3 = on delete (institution)","example":1},"note":{"type":"string","description":"Reason provided by the admin (required for manual adjustments)"}}},"AccountTransferListRequest":{"type":"object","properties":{"account":{"type":"string","description":"Filter by account ID"},"institution":{"type":"string","description":"Filter by institution ID"},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-based)"},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":50,"description":"Number of results per page"},"limitsearch":{"type":"number","description":"DEPRECATED: Use pageSize instead","deprecated":true}}},"AccountTransferListResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/AccountTransfer"}},"total":{"type":"integer","description":"Total number of matching transfers"},"hasMore":{"type":"boolean","description":"Whether more results are available beyond the current page"},"page":{"type":"integer","description":"Current page number"},"pageSize":{"type":"integer","description":"Number of results per page"},"message":{"type":"string"}}},"AIModelFeatures":{"type":"object","description":"Capability flags advertised by the model.","properties":{"stream":{"type":"boolean","default":false,"description":"Supports streaming responses."},"tools":{"type":"boolean","default":false,"description":"Supports tool / function calling."},"code":{"type":"boolean","default":false,"description":"Supports a built-in code-execution tool."},"vision":{"type":"boolean","default":false,"description":"Supports image inputs."},"mcp":{"type":"boolean","default":false,"description":"Supports Model Context Protocol tool servers."}}},"AIModelInputMedia":{"type":"object","description":"Input modalities the model accepts.","properties":{"text":{"type":"boolean","default":true},"image":{"type":"boolean","default":false},"audio":{"type":"boolean","default":false},"video":{"type":"boolean","default":false},"pdf":{"type":"boolean","default":false}}},"AIModelOutputMedia":{"type":"object","description":"Output modalities the model can produce.","properties":{"text":{"type":"boolean","default":true},"audio":{"type":"boolean","default":false},"video":{"type":"boolean","default":false},"image":{"type":"boolean","default":false}}},"AIModel":{"type":"object","properties":{"_id":{"type":"string","description":"AI Model ID"},"name":{"type":"string","description":"Model name (unique per institution + model_use). For example, the published model identifier sent to the provider (e.g. \"gpt-4o\", \"claude-sonnet-4-5\")."},"institution":{"oneOf":[{"type":"string","description":"Institution ID (when unpopulated)."},{"type":"object","description":"Populated institution summary (when listed via GET-by-id or paginated list).","properties":{"_id":{"type":"string"},"name":{"type":"string"},"ainame":{"type":"string"},"picture":{"type":"string"}}}]},"model_use":{"type":"string","enum":["conversationModel","imageGenerationModel","audioGenerationModel","videoGenerationModel","imageAnalysisModel","embeddingsModel","audioAnalysisModel","ttsModel","summaryModel","rtModel","moderationModel"],"default":"conversationModel","description":"Functional role this model plays in the dispatch pipeline."},"description":{"type":"string","default":"","description":"Free-form description of the model."},"api_url":{"type":"string","description":"Base URL of the provider endpoint. Required for openai_cli custom models; optional/ignored for bedrock_cli.","example":"https://api.openai.com/v1"},"api_key":{"type":"string","description":"Provider API key. Required for openai_cli, mistral_cli, and xai_cli custom models. Not required for bedrock_cli (uses AWS credentials)."},"default_headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Optional flat string-string map of extra HTTP request headers passed verbatim on every call. Useful for OpenAI-compatible proxies that require an API-version header (Cohere, Azure, some LiteLLM setups). Currently honored by the openai_cli client builder."},"client_library":{"type":"string","enum":["openai_cli","anthropic_cli","bedrock_cli","google_cli","mistral_cli","xai_cli","stability_cli","lemonslice_cli"],"default":"openai_cli","description":"SDK / dispatch backend used to talk to the provider."},"features":{"$ref":"#/components/schemas/AIModelFeatures"},"input_media":{"$ref":"#/components/schemas/AIModelInputMedia"},"output_media":{"$ref":"#/components/schemas/AIModelOutputMedia"},"max_input_tokens":{"type":"integer","default":128000,"description":"Maximum input (context) tokens supported by the model."},"max_output_tokens":{"type":"integer","default":64000,"description":"Maximum output tokens the model will emit in a single response."},"reasoning_effort":{"type":"string","enum":["none","low","medium","high","max"],"nullable":true,"default":null,"description":"Default reasoning-effort hint passed to providers that accept it (e.g. OpenAI o-series, xAI grok-3-mini)."},"extended_context":{"type":"boolean","default":false,"description":"Whether the model's extended-context tier is enabled."},"status":{"type":"string","enum":["active","inactive","deleted"],"default":"active","description":"Lifecycle status. Deletes are soft — name is prefixed with the timestamp and status flipped to \"deleted\"."},"picture_url":{"type":"string","description":"Optional avatar / icon URL shown in the admin UI."},"created":{"type":"string","format":"date-time","description":"Creation timestamp"}}},"AIModelCreateRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Model name (must be unique per institution + model_use)."},"institution":{"type":"string","description":"Institution ID. Defaults to the caller's institution when omitted."},"model_use":{"type":"string","enum":["conversationModel","imageGenerationModel","audioGenerationModel","videoGenerationModel","imageAnalysisModel","embeddingsModel","audioAnalysisModel","ttsModel","summaryModel","rtModel","moderationModel"],"default":"conversationModel"},"description":{"type":"string"},"client_library":{"type":"string","enum":["openai_cli","anthropic_cli","bedrock_cli","google_cli","mistral_cli","xai_cli","stability_cli","lemonslice_cli"],"default":"openai_cli","description":"SDK / dispatch backend used to talk to the provider."},"api_url":{"type":"string","description":"Provider base URL. Required for openai_cli custom models."},"api_key":{"type":"string","description":"Provider API key. Required for openai_cli, mistral_cli, and xai_cli custom models."},"default_headers":{"type":"object","additionalProperties":{"type":"string"}},"features":{"$ref":"#/components/schemas/AIModelFeatures"},"input_media":{"$ref":"#/components/schemas/AIModelInputMedia"},"output_media":{"$ref":"#/components/schemas/AIModelOutputMedia"},"max_input_tokens":{"type":"integer","default":128000},"max_output_tokens":{"type":"integer","default":64000},"reasoning_effort":{"type":"string","enum":["none","low","medium","high","max"],"nullable":true},"extended_context":{"type":"boolean","default":false},"status":{"type":"string","enum":["active","inactive"],"default":"active"},"picture_url":{"type":"string"}}},"AIModelUpdateRequest":{"type":"object","description":"Partial update. Every field is optional; only the keys provided are written. The handler merges client_library / api_url / api_key with the stored document before re-validating, so you can update one field without resending the others.","properties":{"name":{"type":"string"},"institution":{"type":"string"},"model_use":{"type":"string","enum":["conversationModel","imageGenerationModel","audioGenerationModel","videoGenerationModel","imageAnalysisModel","embeddingsModel","audioAnalysisModel","ttsModel","summaryModel","rtModel","moderationModel"]},"description":{"type":"string"},"client_library":{"type":"string","enum":["openai_cli","anthropic_cli","bedrock_cli","google_cli","mistral_cli","xai_cli","stability_cli","lemonslice_cli"]},"api_url":{"type":"string"},"api_key":{"type":"string"},"default_headers":{"type":"object","additionalProperties":{"type":"string"}},"features":{"$ref":"#/components/schemas/AIModelFeatures"},"input_media":{"$ref":"#/components/schemas/AIModelInputMedia"},"output_media":{"$ref":"#/components/schemas/AIModelOutputMedia"},"max_input_tokens":{"type":"integer"},"max_output_tokens":{"type":"integer"},"reasoning_effort":{"type":"string","enum":["none","low","medium","high","max"],"nullable":true},"extended_context":{"type":"boolean"},"status":{"type":"string","enum":["active","inactive"]},"picture_url":{"type":"string"}}},"AIModelListRequest":{"type":"object","properties":{"institution":{"type":"string","description":"Filter by institution ID. When omitted, admins fall back to their own institution and supers see all."},"account":{"type":"string","description":"Filter by account IDs (space-separated). Resolves to every non-deleted institution under those accounts, then filters by the caller's RAP."},"minimum":{"type":"boolean","description":"When true, returns a flat list of `{ _id, name, model_use, status }` with no pagination. Used by dropdown selectors."},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-based, ignored when minimum=true)."},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":100,"description":"Page size (also accepted as `limitsearch`; ignored when minimum=true)."}}},"AIModelCreateResponse":{"type":"object","properties":{"success":{"type":"boolean"},"id":{"type":"string","description":"The new AI model's ObjectId."},"message":{"type":"string","example":"AIModel created!"}}},"AIModelListResponse":{"type":"object","description":"Paginated response (returned when `minimum` is false or omitted).","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/AIModel"}},"total":{"type":"integer","description":"Total number of matching AI models."},"hasMore":{"type":"boolean","description":"Whether more results are available."},"page":{"type":"integer","description":"Current page number."},"pageSize":{"type":"integer","description":"Number of results per page."},"message":{"type":"string"}}},"AIModelListMinimumResponse":{"type":"object","description":"Minimum-mode response (returned when `minimum=true`). No pagination fields; `data` items are projected to `{ _id, name, model_use, status }`.","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"model_use":{"type":"string"},"status":{"type":"string"}}}},"message":{"type":"string"}}},"AIModelResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/AIModel"}}},"AdminFeedback":{"type":"object","properties":{"_id":{"type":"string","description":"Feedback ID"},"user":{"type":"object","properties":{"_id":{"type":"string"},"email":{"type":"string"},"fname":{"type":"string"},"lname":{"type":"string"}}},"institution":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"}}},"feedback":{"type":"string","description":"Feedback text"},"rating":{"type":"integer","minimum":1,"maximum":5,"description":"Rating value"},"category":{"type":"string","description":"Feedback category"},"status":{"type":"string","enum":["active","deleted"],"description":"Feedback status. Soft-delete sets this to `deleted`."},"context":{"type":"object","description":"Context information"},"response":{"type":"string","description":"Admin response to the feedback (optional, set via updateFeedback)."},"created":{"type":"string","format":"date-time","description":"Submission timestamp"}}},"AdminFeedbackResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/AdminFeedback"}}},"AdminFeedbackUpdateRequest":{"type":"object","description":"Whitelist-enforced update — only `rating`, `comment`, `status`, and `response` are accepted. Any other field is silently dropped.","properties":{"rating":{"type":"integer","minimum":1,"maximum":5,"description":"Updated rating value."},"comment":{"type":"string","description":"Updated feedback text."},"status":{"type":"string","enum":["active","deleted"],"description":"Updated status. Use `deleted` to soft-delete."},"response":{"type":"string","description":"Admin response visible to the user."}}},"InstitutionCreateRequest":{"type":"object","required":["name","account"],"properties":{"name":{"type":"string","description":"Institution name/domain"},"account":{"type":"string","description":"Account ID reference"},"status":{"type":"string","enum":["active","inactive"],"default":"active"},"credits":{"type":"integer","description":"Initial credits"},"publicId":{"type":"string","description":"Public UUID identifier"},"ainame":{"type":"string","description":"AI assistant name","default":"Pria"},"contactEmail":{"type":"string","format":"email","description":"Contact email"},"poolCredits":{"type":"boolean","description":"Pool credits between users","default":true},"creditCaps":{"type":"object","description":"Per-institution user-quota configuration. Editable by admins with the institutions.edit entitlement when the account has not enabled caps; read-only (inherited) when the account owns caps.","properties":{"enabled":{"type":"boolean","default":false},"perUserInstitution":{"type":"number","nullable":true},"perUser24h":{"type":"number","nullable":true}}}}},"InstitutionCreateResponse":{"type":"object","properties":{"success":{"type":"boolean"},"id":{"type":"string","description":"New institution ID"},"message":{"type":"string"}}},"GenerateAboutRequest":{"type":"object","required":["institution"],"properties":{"institution":{"type":"string","description":"Institution ID"}}},"GenerateAboutResponse":{"type":"object","properties":{"success":{"type":"boolean"},"about":{"type":"string","description":"Generated about text"}}},"FreeCreditsRequest":{"type":"object","properties":{"credits":{"type":"integer","description":"Number of free credits to award"}}},"CloneInstitutionResponse":{"type":"object","properties":{"success":{"type":"boolean"},"id":{"type":"string","description":"New cloned institution ID"},"message":{"type":"string"}}},"UpdateInstitutionAccountRequest":{"type":"object","required":["account"],"properties":{"account":{"type":"string","nullable":true,"description":"Target account ID (or null to detach — super-only)"}}},"UpdateInstitutionAccountResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"ainame":{"type":"string"},"lastActivityAt":{"type":"string","format":"date-time"},"account":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"}}}}},"message":{"type":"string"}}},"GeneratePromptRequest":{"type":"object","required":["institution"],"properties":{"institution":{"type":"string","description":"Institution ID"}}},"GeneratePromptResponse":{"type":"object","properties":{"success":{"type":"boolean"},"prompt":{"type":"string","description":"Generated system prompt"}}},"AnalyzePromptRequest":{"type":"object","required":["prompt"],"properties":{"prompt":{"type":"string","description":"Prompt text to analyze"},"institution":{"type":"string","description":"Institution ID for context"}}},"AnalyzePromptResponse":{"type":"object","properties":{"success":{"type":"boolean"},"analysis":{"type":"object","description":"Prompt analysis results","properties":{"quality":{"type":"number","description":"Quality score"},"suggestions":{"type":"array","items":{"type":"string"},"description":"Improvement suggestions"}}}}},"Chart":{"type":"object","properties":{"_id":{"type":"string","description":"Chart ID"},"name":{"type":"string","description":"Chart name"},"type":{"type":"string","description":"Chart type"},"data":{"type":"object","description":"Chart data"},"institution":{"type":"string","description":"Institution ID"},"created":{"type":"string","format":"date-time"}}},"ChartListRequest":{"type":"object","properties":{"institution":{"type":"string","description":"Filter by institution ID"},"minimum":{"type":"boolean"}}},"ChartListResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Chart"}}}},"AgentWorkspacePreferences":{"type":"object","properties":{"cpu":{"type":"number","minimum":0.25,"maximum":4},"memoryMb":{"type":"integer","minimum":512,"maximum":8192},"storageGb":{"type":"integer","minimum":5,"maximum":100},"gui":{"type":"boolean"},"vncResolution":{"type":"string","example":"1280x800"}}},"AgentWorkspaceActivationRequest":{"type":"object","properties":{"preset":{"type":"string","enum":["default","developer","browser-worker"]},"manifestYaml":{"type":"string","description":"OpenAPI YAML workspace manifest; stored as declarative data and not executed by Pria."},"preferences":{"$ref":"#/components/schemas/AgentWorkspacePreferences"}}},"AgentWorkspaceDisableRequest":{"type":"object","properties":{"reason":{"type":"string","maxLength":2000}}},"AgentWorkspaceAgentState":{"type":"object","properties":{"enabled":{"type":"boolean"},"status":{"type":"string","enum":["not_enabled","requested","provisioning","ready","failed","disabled"]},"preset":{"type":"string"},"manifestYaml":{"type":"string"},"manifestDigest":{"type":"string"},"preferences":{"$ref":"#/components/schemas/AgentWorkspacePreferences"},"requestedBy":{"type":"string"},"requestedAt":{"type":"string","format":"date-time"},"provisionedAt":{"type":"string","format":"date-time"},"lastError":{"type":"string"}}},"AgentWorkspaceAgentResponse":{"type":"object","properties":{"success":{"type":"boolean"},"idempotent":{"type":"boolean"},"agent":{"$ref":"#/components/schemas/AgentWorkspaceAgentState"}}},"MCPServer":{"type":"object","properties":{"_id":{"type":"string","description":"MCP Server ID"},"name":{"type":"string","description":"Server name (trimmed, ≤300 chars, unique per institution)."},"institution":{"type":"string","description":"Institution ObjectId this server belongs to."},"user":{"type":"string","description":"User ObjectId that registered the server (optional)."},"server_type":{"type":"string","default":"url","description":"Server transport type (currently always `url`)."},"server_url":{"type":"string","description":"MCP server URL endpoint."},"require_approval":{"type":"object","description":"Per-call approval configuration.","properties":{"enabled":{"type":"boolean","default":false},"skip_approval_tools":{"type":"array","items":{"type":"string"},"description":"Tool names that bypass approval when `enabled: true`."}}},"authorization_header":{"type":"string","description":"Bearer/auth header value forwarded with every MCP call."},"tool_configuration":{"type":"object","description":"Tool exposure configuration.","properties":{"enabled":{"type":"boolean","default":true},"allowed_tools":{"type":"array","items":{"type":"string"},"description":"Whitelist of tool names exposed from this server. Empty array allows all."}}},"description":{"type":"string","description":"Free-form server description."},"status":{"type":"string","enum":["active","inactive","deleted"],"description":"Server status. Soft-delete sets `deleted`."},"created":{"type":"string","format":"date-time","description":"Creation timestamp"}}},"MCPServerCreateRequest":{"type":"object","required":["name","server_url"],"properties":{"name":{"type":"string","description":"Server name (unique per institution)."},"server_url":{"type":"string","description":"MCP server URL endpoint."},"server_type":{"type":"string","default":"url"},"institution":{"type":"string","description":"Institution ObjectId."},"require_approval":{"type":"object","properties":{"enabled":{"type":"boolean"},"skip_approval_tools":{"type":"array","items":{"type":"string"}}}},"authorization_header":{"type":"string"},"tool_configuration":{"type":"object","properties":{"enabled":{"type":"boolean"},"allowed_tools":{"type":"array","items":{"type":"string"}}}},"description":{"type":"string"},"status":{"type":"string","enum":["active","inactive","deleted"],"default":"active"}}},"MCPServerListRequest":{"type":"object","properties":{"institution":{"type":"string","description":"Filter by institution ID"},"minimum":{"type":"boolean","description":"Return minimal data"}}},"MCPServerListResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/MCPServer"}}}},"MCPServerResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/MCPServer"}}},"AdminMemoryRow":{"type":"object","properties":{"_id":{"type":"string"},"key_name":{"type":"string"},"key_value":{"type":"string"},"key_description":{"type":"string"},"key_namespace":{"type":"string"},"shared":{"type":"boolean"},"created":{"type":"string","format":"date-time"},"institution":{"nullable":true,"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"ainame":{"type":"string"}}}}},"AdminInstitutionMemoryRow":{"type":"object","properties":{"_id":{"type":"string"},"key_name":{"type":"string"},"key_value":{"type":"string"},"key_description":{"type":"string"},"key_namespace":{"type":"string"},"shared":{"type":"boolean","enum":[true]},"created":{"type":"string","format":"date-time"},"author":{"type":"object","properties":{"_id":{"type":"string"},"email":{"type":"string"},"fname":{"type":"string"},"lname":{"type":"string"}}},"authorEditable":{"type":"boolean","description":"True iff the requesting admin holds users.edit on at least one institution where the author has membership (or is super). Drives the NavLink shortcut in the UI."}}},"Session":{"type":"object","description":"Web session row tracked per (userInstitution, sessionId) tuple. Created on first request from a browser/UA and updated on every authenticated hit (lastUpdated bump). Soft-deleted (status:'deleted') by DELETE /api/admin/session/{id}.","properties":{"_id":{"type":"string","description":"Session ID (Mongo ObjectId)"},"sessionId":{"type":"string","description":"Opaque per-browser session identifier (cookie-bound). Unique within a userInstitution."},"userInstitution":{"type":"string","description":"UserInstitution ID — the (user, institution) pair this session belongs to"},"user":{"oneOf":[{"type":"string"},{"type":"object"}],"description":"User ID. Populated on GET /api/admin/session/{id} with { email, fname, lname, accountType, status }.","properties":{"_id":{"type":"string"},"email":{"type":"string","format":"email"},"fname":{"type":"string"},"lname":{"type":"string"},"accountType":{"type":"string"},"status":{"type":"string"}}},"userEmail":{"type":"string","format":"email","description":"Denormalized owner email for search"},"userFirst":{"type":"string","description":"Denormalized owner first name for search"},"userLast":{"type":"string","description":"Denormalized owner last name for search"},"institution":{"oneOf":[{"type":"string"},{"type":"object"}],"description":"Institution ID. Populated on GET /api/admin/session/{id} with { name, ainame, credits, poolCredits, status } and on POST /api/admin/sessions with { name, poolCredits, status, ainame }.","properties":{"_id":{"type":"string"},"name":{"type":"string"},"ainame":{"type":"string"},"credits":{"type":"number"},"poolCredits":{"type":"number"},"status":{"type":"string"}}},"status":{"type":"string","enum":["active","deleted"],"description":"Persisted session status. Defaults to 'active' on create; the DELETE endpoint flips it to 'deleted' (soft delete tombstone). The schema's free-form String type can theoretically hold other values (e.g. 'inactive', 'error') but no live code path writes them."},"sourceIp":{"type":"string","description":"Source IP captured when the session was created"},"ua":{"type":"string","description":"Raw User-Agent header (omitted from POST /api/admin/sessions list responses via -ua projection)"},"browserName":{"type":"string","description":"Parsed browser name (ua-parser-js)"},"browserVersion":{"type":"string","description":"Parsed browser version"},"engineName":{"type":"string","description":"Parsed browser engine name"},"engineVersion":{"type":"string","description":"Parsed browser engine version"},"osName":{"type":"string","description":"Parsed operating system name"},"osVersion":{"type":"string","description":"Parsed operating system version"},"deviceModel":{"type":"string","description":"Parsed device model"},"deviceType":{"type":"string","description":"Parsed device type (e.g. mobile, tablet) — undefined for desktop browsers"},"deviceVendor":{"type":"string","description":"Parsed device vendor"},"cpuArchitecture":{"type":"string","description":"Parsed CPU architecture"},"created":{"type":"string","format":"date-time","description":"Session creation timestamp"},"lastUpdated":{"type":"string","format":"date-time","description":"Most recent activity timestamp (refreshed on every authenticated request)"},"lastKA":{"type":"string","format":"date-time","description":"Last known-active timestamp lifted from the related userInstitution document (only present on POST /api/admin/sessions list responses, sourced via the userInstitution populate)"},"user_data":{"type":"object","description":"Trimmed owner block shaped on the POST /api/admin/sessions list response (built from denormalized userEmail/userFirst/userLast + the user ObjectId)","properties":{"_id":{"type":"string"},"email":{"type":"string","format":"email"},"fname":{"type":"string"},"lname":{"type":"string"}}},"institution_data":{"type":"object","description":"Trimmed institution block shaped on the POST /api/admin/sessions list response","properties":{"_id":{"type":"string"},"name":{"type":"string"},"ainame":{"type":"string"}}}}},"SessionResponse":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"$ref":"#/components/schemas/Session"}}},"SSOProvider":{"type":"object","properties":{"_id":{"type":"string","description":"SSO Provider ID"},"institution":{"type":"string","description":"Institution ID"},"slug":{"type":"string","description":"URL slug for SSO login (lowercase, alphanumeric and hyphens only)","example":"my-university"},"label":{"type":"string","description":"Display label"},"enabled":{"type":"boolean","description":"Whether SSO login is enabled"},"clientId":{"type":"string","description":"OAuth Client ID"},"clientSecret":{"type":"string","description":"OAuth Client Secret (masked in list responses)"},"tokenHost":{"type":"string","description":"OAuth token endpoint host","example":"https://id.provider.com:443"},"tokenPath":{"type":"string","description":"OAuth token endpoint path","default":"/oauth2/token"},"authorizePath":{"type":"string","description":"OAuth authorize endpoint path","default":"/oauth2/authorize"},"userinfoUrl":{"type":"string","description":"Full URL for user info endpoint","example":"https://id.provider.com:443/oauth2/userinfo"},"scope":{"type":"string","description":"OAuth scope","default":"openid"},"fieldMapping":{"type":"object","description":"Maps IdP user info fields to Pria user fields","properties":{"email":{"type":"string","default":"email"},"firstName":{"type":"string","default":"given_name"},"lastName":{"type":"string","default":"family_name"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"SSOProviderCreateRequest":{"type":"object","required":["institution","slug","clientId","clientSecret","tokenHost","userinfoUrl"],"properties":{"institution":{"type":"string","description":"Institution ID (immutable after creation)"},"slug":{"type":"string","description":"URL slug (lowercase letters, numbers, hyphens only)","pattern":"^[a-z0-9-]+$"},"label":{"type":"string"},"enabled":{"type":"boolean"},"clientId":{"type":"string"},"clientSecret":{"type":"string"},"tokenHost":{"type":"string"},"tokenPath":{"type":"string"},"authorizePath":{"type":"string"},"userinfoUrl":{"type":"string"},"scope":{"type":"string"},"fieldMapping":{"type":"object","properties":{"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"}}}}},"SSOProviderListRequest":{"type":"object","properties":{"institution":{"type":"string","description":"Filter by institution ID"},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-based)"},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":100,"description":"Number of results per page"}}},"SSOProviderListResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Request success status"},"data":{"type":"array","items":{"$ref":"#/components/schemas/SSOProvider"},"description":"Array of SSO provider objects"},"total":{"type":"integer","description":"Total number of matching providers"},"hasMore":{"type":"boolean","description":"Whether more results are available"},"page":{"type":"integer","description":"Current page number"},"pageSize":{"type":"integer","description":"Number of results per page"},"message":{"type":"string","description":"Response message"}}},"Institution":{"type":"object","properties":{"_id":{"type":"string","description":"Institution ID"},"name":{"type":"string","description":"Institution name"},"picture":{"type":"string","description":"Institution picture URL","format":"uri"},"credits":{"type":"integer","description":"Available credits for the institution"},"creditsUsed":{"type":"integer","description":"Number of credits used"},"creditsAwarded":{"type":"integer","description":"Credits awarded to the institution"},"status":{"type":"string","enum":["active","inactive"],"description":"Institution status"},"publicId":{"type":"string","description":"Public identifier for the institution"},"publicAuthorizedUrls":{"type":"array","items":{"type":"string"},"description":"List of authorized URLs for the institution"},"ainame":{"type":"string","description":"AI name for the institution"},"contactEmail":{"type":"string","description":"Contact email for the institution"},"poolCredits":{"type":"boolean","description":"Whether credits are pooled"},"created":{"type":"string","format":"date-time","description":"Institution creation timestamp"},"lastActivityAt":{"type":"string","format":"date-time","nullable":true,"description":"Timestamp of the most recent activity (history save) cascaded from users. Null if no activity has been recorded."},"conversationModel":{"type":"string","description":"AI conversation model used"},"account":{"$ref":"#/components/schemas/Account"},"agent":{"$ref":"#/components/schemas/AgentWorkspaceAgentState","nullable":true,"description":"Agent workspace desired-state configuration for this institution, when activation has been requested."},"maxCompletionTokens":{"type":"integer","description":"Maximum completion tokens allowed"},"creditsTotal":{"type":"number","description":"Total credits available","nullable":true},"creditsUsagePct":{"type":"number","description":"Percentage of credits used"},"id":{"type":"string","description":"Institution ID"},"displayAgentDetails":{"type":"boolean","description":"Whether to display persisted tool details on saved History records"},"displayThinkingDetails":{"type":"boolean","description":"Whether to display persisted model thinking/reasoning details on saved History records"},"displayRagSearchDetails":{"type":"boolean","description":"Whether to display persisted RAG/KAG retrieval segments on saved History records"},"displayThinkingExecution":{"type":"boolean","description":"Whether to display the live thinking trace while a response is streaming"},"displayToolExecution":{"type":"boolean","description":"Whether to display live tool execution indicators while a response is streaming"}}},"InstitutionListRequest":{"type":"object","properties":{"minimum":{"type":"boolean","description":"Whether to return minimal institution data (bypasses pagination)"},"compact":{"type":"boolean","description":"Whether to return compact institution data"},"account":{"type":"string","description":"Account ID(s) to filter institutions (space-separated for multiple)"},"institutionsearch":{"type":"string","description":"Search term for institutions (matches name, AI name, publicId)"},"statusFilter":{"type":"string","enum":["active","inactive","all"],"default":"active","description":"Filter institutions by lifecycle status. 'active' returns only active instances, 'inactive' returns only inactive, 'all' returns both. Deleted institutions are always excluded."},"activeOnly":{"type":"boolean","default":false,"description":"When true, restrict to instances that have actually been used (lastActivityAt is set, not null). ANDs with statusFilter."},"withCustomModel":{"type":"boolean","default":false,"description":"When true, restrict to institutions that have a non-empty conversationModel set. Combines with statusFilter and notPoolCredits via AND."},"notPoolCredits":{"type":"boolean","default":false,"description":"When true, restrict to institutions where poolCredits is not true (i.e. false or unset). Combines with statusFilter and withCustomModel via AND."},"hasThreats":{"type":"boolean","default":false,"description":"When true, narrow the RAP-scoped result to institutions that have threat incidents (intersection with distinct threatMonitoringIncident.institutionIds). Backs the Praxis Shield panel's instance selector; only ever narrows the scoped set."},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-based)"},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":100,"description":"Number of results per page"},"limitsearch":{"type":"number","description":"DEPRECATED: Use pageSize instead","deprecated":true}}},"InstitutionListResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the request was successful"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Institution"},"description":"Array of institution objects"},"total":{"type":"integer","description":"Total number of matching institutions"},"hasMore":{"type":"boolean","description":"Whether more results are available beyond the current page"},"page":{"type":"integer","description":"Current page number"},"pageSize":{"type":"integer","description":"Number of results per page"},"message":{"type":"string","description":"Response message"}}},"InstitutionResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the request was successful"},"data":{"$ref":"#/components/schemas/Institution"}}},"InstitutionUpdateRequest":{"type":"object","required":["name","account","status","credits","publicId"],"properties":{"name":{"type":"string","description":"Institution domain name","example":"28615560.marcodivittorio.utoronto-dev.instructure.com"},"account":{"type":"string","description":"Account ID reference","example":"66cffaa6b359c109fb06d839"},"status":{"type":"string","enum":["active","inactive"],"description":"Institution status","example":"inactive"},"credits":{"type":"integer","description":"Number of credits allocated","example":50},"publicId":{"type":"string","description":"Public UUID identifier","example":"c98291e9-12e7-4304-b47b-ab18b0968a02"},"publicAuthorizedUrls":{"type":"array","items":{"type":"string"},"description":"List of authorized URLs","example":["https://utoronto-dev.instructure.com","https://canvas.instructure.com"]},"picture":{"type":"string","description":"Institution picture URL"},"picture_bg":{"type":"string","description":"Background picture URL"},"picture_dark_bg":{"type":"string","description":"Dark mode background picture URL"},"picture_animated":{"type":"string","description":"Animated picture URL"},"elevenlabs_agent_id":{"type":"string","description":"ElevenLabs Conversational AI Agent ID for voice-enabled digital twin","example":"agent_0201kedjfdgee87bjke8acq3pcbg"},"ainame":{"type":"string","description":"AI assistant name","example":"Pria"},"prompt":{"type":"string","description":"Default AI prompt","example":"What can I do for you?"},"contactEmail":{"type":"string","format":"email","description":"Contact email address","example":"marco.divittorio@utoronto.ca"},"creditAward":{"type":"integer","description":"Credit award amount","example":0},"poolCredits":{"type":"boolean","description":"Whether to pool credits","example":true},"kmeanScore":{"type":"number","format":"float","description":"K-means clustering score","example":0.45},"disableFileUploadForUser":{"type":"boolean","description":"Disable file upload for users","example":false},"disableAudioNotesForUser":{"type":"boolean","description":"Disable Audio Notes capture (microphone icon in toolbar) for standard users in this institution. Admins are unaffected. Audio Notes is also hidden when guestUI or disableFileUploadForUser is on.","example":false},"disableAssistantsForUser":{"type":"boolean","description":"Disable assistants for users","example":false},"ltiContextIds":{"type":"array","items":{"type":"string"},"description":"LTI context identifiers"},"personalisationAsked":{"type":"boolean","description":"Whether personalization was asked","example":false},"kalturaIsAdminSecret":{"type":"boolean","description":"Kaltura admin secret flag","example":false},"kalturaUserId":{"type":"string","description":"Kaltura user ID"},"entitleNewAdmins":{"type":"boolean","description":"Entitle new administrators","example":false},"locationEnabled":{"type":"boolean","description":"Location services enabled","example":true},"enableModeration":{"type":"boolean","description":"Content moderation enabled","example":false},"ragLimitChunks":{"type":"integer","description":"RAG limit chunks","example":2},"kagFusionEnabled":{"type":"boolean","description":"When true, RAG fans out to dense + graph retrievers and fuses via Reciprocal Rank Fusion (KAG). Default false; opt-in per institution.","example":false},"maxFiles":{"type":"integer","description":"Maximum number of files","example":300},"alwaysCiteSources":{"type":"boolean","description":"Always cite sources","example":true},"canvasAPIScope":{"type":"array","items":{"type":"string"},"description":"Canvas API scope permissions"},"rtEnabled":{"type":"boolean","description":"Real-time features enabled","example":false},"rtAdminOnly":{"type":"boolean","description":"Real-time admin only","example":true},"displayAgentDetails":{"type":"boolean","description":"Display persisted tool details on saved History records","example":true},"displayThinkingDetails":{"type":"boolean","description":"Display persisted model thinking/reasoning details on saved History records","example":true},"displayRagSearchDetails":{"type":"boolean","description":"Display persisted RAG/KAG retrieval segments (file + relevance + chunk preview) on saved History records","example":true},"displayThinkingExecution":{"type":"boolean","description":"Display the live thinking trace while a response is streaming (independent of displayThinkingDetails)","example":true},"displayToolExecution":{"type":"boolean","description":"Display live tool execution indicators while a response is streaming (independent of displayAgentDetails)","example":true},"rtVoice":{"type":"string","description":"Real-time voice setting"},"maxCompletionTokens":{"type":"integer","description":"Maximum completion tokens (-1 for unlimited)","example":-1},"allowJoining":{"type":"string","enum":["enabled","disabled"],"description":"Allow joining setting","example":"disabled"},"joiningAdminOnly":{"type":"boolean","description":"Joining admin only","example":false},"questionType":{"type":"string","enum":["PERSONA"],"description":"Onboarding question bank this institution uses (single live bank — \"PERSONA\"; legacy INSTITUTION/CORPORATE banks are retired)"},"creditCaps":{"type":"object","description":"Per-institution user-quota configuration. Editable by admins with the institutions.edit entitlement when the account has not enabled caps; read-only (inherited) when the account owns caps.","properties":{"enabled":{"type":"boolean","default":false,"description":"When true and the account has not enabled caps, per-user quotas are enforced for this institution's members."},"perUserInstitution":{"type":"number","nullable":true,"description":"Maximum lifetime credits a user may consume in this institution. Null or 0 = no cap."},"perUser24h":{"type":"number","nullable":true,"description":"Maximum credits a user may consume in any rolling 24-hour window. Null or 0 = no cap. Auto-recovers."}}}}},"InstitutionUpdateResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"id":{"type":"string","description":"ID of the updated institution","example":"67826dfe5d387207aabd6fc1"}}},"Question":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier for the question"},"code":{"type":"string","description":"Question code identifier"},"question":{"type":"string","description":"The actual question text"},"objectif":{"type":"string","description":"Objective or purpose of the question"},"required":{"type":"boolean","description":"Whether the question is required"},"status":{"type":"string","description":"Status of the question (active/inactive)"},"type":{"type":"string","enum":["text","single_choice","multi_choice","slider_group"],"default":"text","description":"Answer control type. Absent/omitted renders as plain text. single_choice/multi_choice render selectable tiles; slider_group renders 1-7 scale rows."},"config":{"type":"object","description":"Per-type authoring data. single_choice/multi_choice: { choices: [{ value, label, image? }] }. slider_group: { scales: [{ key, label, lowLabel, highLabel }] }. text: { multiline?: boolean }. Validated server-side on save."},"imageUrl":{"type":"string","description":"Optional banner image shown above the question on every surface."},"position":{"type":"integer","description":"Position order of the question"},"created":{"type":"string","format":"date-time","description":"Creation timestamp"},"section":{"type":"integer","description":"Section number the question belongs to"},"mbti":{"type":"boolean","description":"Whether question is related to MBTI assessment"}}},"InstitutionQuestion":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier for the institution question"},"question":{"$ref":"#/components/schemas/Question"},"institution":{"$ref":"#/components/schemas/Institution"},"answer":{"type":"string","description":"Prompt-facing answer string. For non-text questions this is serialized server-side from answerData (e.g. a slider_group answer becomes \"Tone: 6/7 (Highly expressive and animated); ...\"). The client-supplied answer is never trusted — the backend re-serializes authoritatively."},"answerData":{"description":"Structured answer mirroring the question type. single_choice: the chosen value (string). multi_choice: array of chosen values. slider_group: { [scaleKey]: number } (1-7). text: omitted. answer is derived from this on save.","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}},{"type":"object"}]},"created":{"type":"string","format":"date-time","description":"Creation timestamp"}}},"InstitutionQuestionsResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the request was successful"},"data":{"type":"array","items":{"$ref":"#/components/schemas/InstitutionQuestion"},"description":"Array of institution questions"}}},"InstitutionQuestionsRequest":{"type":"object","properties":{"minimum":{"type":"boolean","description":"Whether to return minimum data"},"institution":{"type":"string","description":"Institution ID to filter questions"}},"required":["institution"]},"QuestionRequest":{"type":"object","properties":{"minimum":{"type":"boolean","description":"Flag to return minimal question data (bypasses pagination, returns all)"},"questionType":{"type":"string","enum":["PERSONA"],"description":"Question bank (code prefix) to retrieve. \"PERSONA\" is the single live onboarding bank; legacy INSTITUTION/CORPORATE banks are retired (inactive) but still visible in the library filter."},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-based, ignored if minimum=true)"},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":100,"description":"Number of results per page (ignored if minimum=true)"}}},"QuestionsResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Question"},"description":"Array of questions"},"total":{"type":"integer","description":"Total number of matching questions (omitted if minimum=true)"},"hasMore":{"type":"boolean","description":"Whether more results are available (omitted if minimum=true)"},"page":{"type":"integer","description":"Current page number (omitted if minimum=true)"},"pageSize":{"type":"integer","description":"Number of results per page (omitted if minimum=true)"},"message":{"type":"string","description":"Response message"}}},"AssistantResponse":{"type":"object","properties":{"_id":{"type":"string","description":"Unique identifier for the assistant"},"name":{"type":"string","description":"Name of the assistant"},"description":{"type":"string","description":"Detailed description of the assistant's capabilities"},"instructions":{"type":"string","description":"Detailed instructions for the assistant's behavior"},"picture_url":{"type":"string","description":"URL path to the assistant's profile picture"},"status":{"type":"string","enum":["active","inactive"],"description":"Current status of the assistant"},"liked_count":{"type":"integer","description":"Number of likes the assistant has received"},"admin_only":{"type":"boolean","description":"Whether the assistant is restricted to admin users only"},"institution_shared":{"type":"boolean","description":"Whether the assistant is shared across institutions"},"remember_history":{"type":"integer","description":"Number of conversation history items to remember"},"created":{"type":"string","format":"date-time","description":"Creation timestamp"},"argument_1":{"type":"string","nullable":true,"description":"Optional argument parameter"},"argument_5":{"type":"string","nullable":true,"description":"Optional argument parameter"},"editable_others":{"type":"boolean","description":"Whether others can edit this assistant"},"ragCollections":{"type":"array","items":{"type":"string"},"description":"Array of Collection ObjectIds to restrict RAG retrieval scope. Empty array means all collections (default behavior)."},"assistantToolMode":{"type":"string","enum":["all","whitelist"],"default":"all","description":"Tool access mode — 'all' uses all institution tools (default), 'whitelist' restricts to tools listed in assistantTools."},"assistantTools":{"type":"array","description":"Per-assistant tool configuration. Only used when assistantToolMode is 'whitelist'.","items":{"type":"object","properties":{"tool":{"type":"string","description":"Tool ObjectId reference"},"instructionsOverride":{"type":"string","description":"Optional override for the tool's instructions field for this assistant"}}}},"history_count":{"type":"integer","description":"Number of conversation histories for this assistant"},"user_data":{"type":"object","nullable":true,"description":"Resolved user information","properties":{"email":{"type":"string"},"fname":{"type":"string"},"lname":{"type":"string"},"institution":{"type":"string"},"accountType":{"type":"string"}}},"institution_data":{"type":"object","nullable":true,"description":"Resolved institution information","properties":{"name":{"type":"string"},"ainame":{"type":"string"},"status":{"type":"string"},"picture":{"type":"string"}}},"account_data":{"type":"object","nullable":true,"description":"Resolved account information","properties":{"name":{"type":"string"},"status":{"type":"string"}}}}},"AssistantListResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"type":"array","items":{"$ref":"#/components/schemas/AssistantResponse"},"description":"Array of assistant objects"},"total":{"type":"integer","description":"Total number of matching assistants"},"hasMore":{"type":"boolean","description":"Whether more results are available"},"page":{"type":"integer","description":"Current page number"},"pageSize":{"type":"integer","description":"Number of results per page"},"message":{"type":"string","description":"Response message"}}},"AssistantFilterRequest":{"type":"object","properties":{"minimum":{"type":"boolean","description":"Whether to return minimal assistant data"},"institution":{"type":"string","description":"Institution ID to filter assistants by"},"account":{"type":"string","description":"Account ID(s) to filter assistants (space-separated for multiple)"},"usersearch":{"type":"string","description":"Search term for users (matches email, first name, last name)"},"admin_only":{"type":"boolean","description":"Filter for admin-only assistants"},"system_only":{"type":"boolean","description":"Filter for system assistants (no owner)"},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-based)"},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":100,"description":"Number of results per page"},"limitsearch":{"type":"integer","description":"Alias for pageSize (deprecated, use pageSize)"}}},"User":{"type":"object","properties":{"_id":{"type":"string","description":"User ID"},"email":{"type":"string","format":"email","description":"User email address"},"fname":{"type":"string","description":"User first name"},"lname":{"type":"string","description":"User last name"},"accountType":{"type":"string","description":"User account type"},"status":{"type":"string","description":"User status"},"lxp_user_id":{"type":"string","description":"LXP user identifier"},"picture":{"type":"string","format":"uri","description":"Profile picture URL"},"lxp_user_type":{"type":"number","description":"LXP user type"},"lxp_partner_id":{"type":"number","description":"LXP partner identifier"},"lxp_partner_name":{"type":"string","description":"LXP partner name"},"lxp_role_id":{"type":"number","description":"LXP role identifier"},"lxp_role_name":{"type":"string","description":"LXP role name"},"credits":{"type":"number","description":"Available credits"},"creditsUsed":{"type":"number","description":"Credits used"},"plan":{"type":"string","description":"Subscription plan"},"institution":{"$ref":"#/components/schemas/Institution"},"remember_history_count":{"type":"number","description":"History count setting"},"created":{"type":"string","format":"date-time","description":"Creation timestamp"}}},"Tool":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","description":"Unique tool name (e.g. get_browser, read_url, call_google_maps)"},"responseLength":{"type":"number"},"responseDurationMs":{"type":"number"},"success":{"type":"boolean"},"_id":{"type":"string","description":"MongoDB ObjectId"},"description":{"type":"string","description":"Short user-facing description of what the tool does"},"instructions":{"type":"string","description":"Long-form instructions to the model on when/how to invoke this tool"},"argument_1":{"type":"string","description":"Natural-language description of the first argument the model should pass"},"argument_2":{"type":"string","description":"Natural-language description of the second argument"},"argument_3":{"type":"string","description":"Natural-language description of the third argument"},"argument_4":{"type":"string","description":"Natural-language description of the fourth argument"},"argument_5":{"type":"string","description":"Natural-language description of the fifth argument"},"argument_6":{"type":"string","description":"Natural-language description of the sixth argument"},"isArray":{"type":"boolean","default":false,"description":"Whether this tool can be invoked multiple times in parallel (array-returning)"},"rtEnabled":{"type":"boolean","default":false,"description":"Whether the tool is exposed in realtime (voice) conversations"},"rtOnly":{"type":"boolean","default":false,"description":"Whether the tool is ONLY available in realtime conversations (hidden from text mode)"},"categories":{"type":"array","items":{"type":"string","enum":["research","productivity","creative","communication","learning"]},"description":"User-facing categories used to filter/group tools in the UI"},"status":{"type":"string","enum":["active","inactive","deleted"],"default":"active","description":"Lifecycle status. `deleted` tools are soft-deleted and excluded from normal listings."},"created":{"type":"string","format":"date-time","description":"Creation timestamp"},"unavailable":{"type":"boolean","description":"Runtime-only flag (not stored). Set by GET /api/admin/tools for Google-service\ntools (call_google_maps, call_google_drive, etc.) when the user's institution\nhas not enabled or connected the required Google Cloud service.\n"},"unavailableReason":{"type":"string","description":"Runtime-only field (not stored). Human-readable reason a Google-service tool\nis unavailable (e.g. \"Google OAuth not configured\",\n\"Google Cloud not enabled for users\", \"Google Cloud not connected for this instance\").\n"}},"description":"A tool definition consumed by the Pria orchestrator. Field names match the\nMongoose schema in routes/models/tool.js. Each `argument_N` is a free-form\nnatural-language description of what value the model should pass for that\npositional argument — the orchestrator forwards it to the LLM as the\nparameter description (NOT a JSON Schema definition).\n"},"ConversationHistory":{"type":"object","properties":{"_id":{"type":"string"},"credits":{"type":"number"},"usage":{"type":"number"},"cached":{"type":"number"},"completion":{"type":"number"},"latencyMs":{"type":"number"},"ragDurationMs":{"type":"number"},"hasRagSearch":{"type":"boolean","description":"True when this turn ran retrieval and produced at least one segment. The full `ragSearch` array is NOT included in list responses — it can be multi-KB per row and is fetched lazily via `GET /api/admin/history/{id}/ragSearch` on `<details>` expand.\n"},"ragSearchCount":{"type":"integer","description":"Number of retrieved segments (used by the UI summary line)."},"ragSearchMode":{"type":"string","enum":["RAG","KAG"],"description":"Retrieval mode this turn was captured under."},"input":{"type":"string"},"inputs":{"type":"array","items":{"type":"string"}},"output":{"type":"string"},"outputs":{"type":"array","items":{"type":"string"}},"success":{"type":"boolean"},"user":{"$ref":"#/components/schemas/User"},"institution":{"$ref":"#/components/schemas/Institution"},"status":{"type":"string"},"query_duration_ms":{"type":"number"},"course_id":{"type":"number"},"course_name":{"type":"string"},"assistant":{"type":"object","nullable":true,"description":"Populated assistant reference","properties":{"_id":{"type":"string"},"name":{"type":"string"}}},"conversation_model":{"type":"string"},"tools":{"type":"array","items":{"$ref":"#/components/schemas/Tool"}},"created":{"type":"string","format":"date-time"},"forgotten":{"type":"boolean","description":"True when the user soft-deleted this conversation from their history. Returned only when includeForgotten=true in the request."}}},"HistoriesResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ConversationHistory"}},"total":{"type":"integer","description":"Total number of matching records across all pages"},"hasMore":{"type":"boolean","description":"Whether more pages exist beyond the current page"},"page":{"type":"integer","description":"Current page number (1-based)"},"pageSize":{"type":"integer","description":"Records per page (resolved/clamped value)"},"message":{"type":"string"}}},"HistoriesRequest":{"type":"object","properties":{"institution":{"type":"string","description":"Institution ID to filter histories (space-separated for multiple)"},"usersearch":{"type":"string","description":"Search term to filter by user email, first name, or last name. Also matches a raw 24-hex user _id (for actioned accounts whose PII has been scrubbed)."},"account":{"type":"string","description":"Account ID to filter by account's institutions (space-separated for multiple)"},"accountType":{"type":"string","enum":["user","admin","super"],"description":"Filter by user account type"},"course_name":{"type":"string","description":"Filter by course name (case-insensitive partial match)"},"includeForgotten":{"type":"boolean","default":false,"description":"When true, include conversations the user soft-deleted (forgotten); each such row carries forgotten=true so the UI can label them. Hidden by default."},"includeInactive":{"type":"boolean","default":false,"description":"Include users of any account status (not just non-deleted) in the usersearch resolution, so inactive/actioned accounts surface. Default false."},"includeDeleted":{"type":"boolean","default":false,"description":"Include soft-deleted (status:'deleted') history rows, not just active. Default false."},"daterange":{"type":"array","items":{"type":"string","format":"date","nullable":true},"minItems":2,"maxItems":2,"description":"Date range filter as [startDate, endDate]. Defaults to last 30 days if not provided. Use null for open-ended ranges.","example":["2026-01-01","2026-01-31"]},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number for pagination (1-based)"},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":1000,"description":"Number of records per page (max 5000). Default 1000 for backward compatibility."},"limit":{"type":"integer","deprecated":true,"description":"Deprecated: use 'pageSize' instead."},"limitsearch":{"type":"integer","deprecated":true,"description":"Deprecated: use 'pageSize' instead."},"summary":{"type":"boolean","description":"When true, returns an AI-generated summary instead of raw data"},"summaryInstruction":{"type":"string","description":"Custom instruction for the AI summary generation"}}},"DeleteHistoryResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful","example":true},"message":{"type":"string","description":"Confirmation message","example":"History deleted"}}},"HistoryData":{"type":"object","properties":{"_id":{"type":"string","description":"History record ID"},"api":{"type":"string","description":"API endpoint used"},"url":{"type":"string","description":"URL endpoint"},"favorite":{"type":"boolean","description":"Whether marked as favorite"},"forgotten":{"type":"boolean","description":"Whether marked as forgotten"},"n":{"type":"number","description":"Number of interactions"},"inputLength":{"type":"number","description":"Length of input text"},"outputLength":{"type":"number","description":"Length of output text"},"credits":{"type":"number","description":"Credits consumed"},"creditsPooled":{"type":"boolean","description":"Whether credits were pooled"},"usage":{"type":"number","description":"Usage metrics"},"cached":{"type":"number","description":"Cached responses count"},"completion":{"type":"number","description":"Completion tokens"},"latencyMs":{"type":"number","description":"Response latency in milliseconds"},"ragDurationMs":{"type":"number","description":"RAG search duration in milliseconds"},"ragSearch":{"type":"array","description":"Structured RAG/KAG retrieval summary for this turn (only when retrieval ran and returned results). Same shape as the runtime spec.","items":{"type":"object","properties":{"uploadId":{"type":"string"},"originalname":{"type":"string"},"chunkIndex":{"type":"integer","nullable":true},"score":{"type":"number"},"length":{"type":"integer"},"mode":{"type":"string","enum":["RAG","KAG"]},"chunkText":{"type":"string"},"confidential":{"type":"boolean"}}}},"price":{"type":"number","description":"Cost of the request"},"input":{"type":"string","description":"User input text"},"inputs":{"type":"array","items":{"type":"string"},"description":"Array of inputs"},"output":{"type":"string","description":"AI output text"},"outputs":{"type":"array","items":{"type":"string"},"description":"Array of outputs"},"code":{"type":"string","description":"Generated code"},"code_language":{"type":"string","description":"Programming language of generated code"},"success":{"type":"boolean","description":"Whether the request was successful"},"user":{"$ref":"#/components/schemas/User"},"institution":{"$ref":"#/components/schemas/Institution"},"status":{"type":"string","description":"Record status"},"query_duration_ms":{"type":"number","description":"Query duration in milliseconds"},"mode":{"type":"number","description":"Operation mode"},"course_id":{"type":"number","description":"Course ID"},"course_name":{"type":"string","description":"Course name"},"conversation_model":{"type":"string","description":"AI model used for conversation"},"tools":{"type":"array","items":{"type":"string"},"description":"Tools used in the conversation"},"created":{"type":"string","format":"date-time","description":"Creation timestamp"}}},"HistoryResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the request was successful"},"data":{"$ref":"#/components/schemas/HistoryData"}}},"ChartDataPoint":{"type":"object","properties":{"_id":{"type":"object","properties":{"granularity":{"type":"string","format":"date-time","description":"The time period for the data point"},"institution":{"type":"string","description":"The institution ID"}}},"dimension":{"type":"number","description":"The count value for this time period"}}},"ChartRequest":{"type":"object","required":["institution","granularity","range","dimension","aggregate"],"properties":{"institution":{"type":"string","description":"Institution ID","example":"65cfebc32f5e1b37d4e52329"},"granularity":{"type":"string","enum":["day","week","month","hour"],"description":"Time granularity for the chart data","example":"month"},"range":{"type":"integer","description":"Time range (negative values for past periods)","example":-3},"dimension":{"type":"string","enum":["count","sum","average"],"description":"Data dimension to measure","example":"count"},"aggregate":{"type":"boolean","description":"Whether to aggregate the data","example":false}}},"ChartResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ChartDataPoint"},"description":"Array of chart data points"}}},"TimelineChartRequest":{"type":"object","required":["granularity","range","dimension"],"properties":{"institution":{"type":"string","description":"Institution ID (optional, filters by institution)","example":"65cfebc32f5e1b37d4e52329"},"account":{"type":"string","description":"Account ID (optional, filters by account's institutions)"},"granularity":{"type":"string","enum":["day","week","month","hour"],"description":"Time granularity for bucketing","example":"month"},"range":{"type":"integer","description":"Time range (negative values for past periods)","example":-3},"dimension":{"type":"string","enum":["count","credits","usage","cached","price"],"description":"Metric to aggregate","example":"count"},"aggregate":{"type":"boolean","description":"When true, aggregates across all institutions","example":false},"topsearch":{"type":"integer","description":"Number of top institutions to return (default 5, -1 for all)","example":5}}},"TotalsChartRequest":{"type":"object","required":["range","dimension"],"properties":{"institution":{"type":"string","description":"Institution ID"},"account":{"type":"string","description":"Account ID"},"granularity":{"type":"string","enum":["day","week","month","hour"],"description":"Used only for date range calculation (dayFactor)","example":"month"},"range":{"type":"integer","description":"Time range (negative values for past periods)","example":-3},"dimension":{"type":"string","enum":["count","credits","usage","cached","price"],"description":"Metric to aggregate","example":"count"},"aggregate":{"type":"boolean","description":"When true, aggregates across all institutions","example":false},"topsearch":{"type":"integer","description":"Number of top institutions to return","example":5}}},"TotalsChartDataPoint":{"type":"object","properties":{"_id":{"type":"object","properties":{"institution":{"type":"string","description":"Institution ID (absent when aggregate=true)"}}},"dimension":{"type":"number","description":"The aggregated metric value"}}},"UsersChartRequest":{"type":"object","required":["granularity","range"],"properties":{"institution":{"type":"string","description":"Institution ID"},"account":{"type":"string","description":"Account ID"},"granularity":{"type":"string","enum":["day","week","month","hour"],"description":"Time granularity for bucketing","example":"month"},"range":{"type":"integer","description":"Time range (negative values for past periods)","example":-3},"aggregate":{"type":"boolean","description":"When true, aggregates across all institutions","example":false},"topsearch":{"type":"integer","description":"Number of top institutions to return","example":5}}},"UserStatsRequest":{"type":"object","required":["granularity"],"properties":{"granularity":{"type":"string","enum":["day","week","month"],"description":"Bucket size for the period column."},"range":{"type":"number","description":"Number of time periods to look back (negative for past).","example":-7},"daterange":{"type":"object","description":"Explicit start/end window. Takes precedence over `range`.","properties":{"start":{"type":"string","format":"date-time"},"end":{"type":"string","format":"date-time"}}},"account":{"type":"string","description":"Space-separated account ids to scope the query."},"institution":{"type":"string","description":"Space-separated institution ids to scope the query."},"accountType":{"type":"string","enum":["user","admin","super"]},"usersearch":{"type":"string","description":"Case-insensitive substring matched against email / fname / lname."}}},"UserStatsResponse":{"type":"object","properties":{"success":{"type":"boolean"},"granularity":{"type":"string","enum":["day","week","month"]},"truncated":{"type":"boolean","description":"True when the row count hit the per-request cap (5000). Narrow the window for full results."},"windowSource":{"type":"string","enum":["daterange","range","default"],"description":"How the date window was derived — explicit `daterange` tuple, relative `range` integer, or a granularity-based default applied because neither was supplied."},"defaultDaysApplied":{"type":"number","nullable":true,"description":"When `windowSource === \"default\"`, the number of days included in the auto-applied window."},"windowStart":{"type":"string","format":"date-time","description":"Inclusive lower bound used for the `created` filter."},"windowEnd":{"type":"string","format":"date-time","description":"Exclusive upper bound used for the `created` filter."},"data":{"type":"array","items":{"type":"object","properties":{"user":{"type":"string","description":"User ObjectId."},"email":{"type":"string"},"fname":{"type":"string"},"lname":{"type":"string"},"institution":{"type":"string","nullable":true,"description":"Institution ObjectId, or null for personal-vault queries."},"instanceName":{"type":"string"},"aiName":{"type":"string"},"period":{"type":"string","enum":["day","week","month"]},"periodStart":{"type":"string","format":"date-time","description":"Inclusive bucket boundary (00:00 UTC for `day`)."},"queryCount":{"type":"number"},"creditsUsed":{"type":"number"},"lifetimeUsed":{"type":"number","description":"Lifetime credits consumed by this user in this institution (from userInstitution.creditConsumed). Scope is lifetime, not the selected period window."},"capLimit":{"type":"number","nullable":true,"description":"Effective per-user-institution cap resolved via pickCapTier. null when caps are off or tier is none."},"capSource":{"type":"string","enum":["account","institution","none"],"description":"Which tier owns the cap — account (account.creditCaps.enabled), institution (institution.creditCaps.enabled), or none (caps off)."},"capUsedPct":{"type":"number","nullable":true,"description":"lifetimeUsed / capLimit. null when capLimit is null."},"atCap":{"type":"boolean","description":"True when capLimit is active and lifetimeUsed >= capLimit."}}}},"summary":{"type":"object","description":"Aggregate statistics over the distinct users in the result set.","properties":{"meanCreditsPerUserAccount":{"type":"number","description":"Mean per-institution lifetime credits (creditConsumed) per distinct user in the result, deduplicated by user (not a cross-institution sum)."},"meanCreditsPerUserInstitution":{"type":"number","description":"Mean lifetime credits per distinct (user, institution) pair in the result."},"usersAtCap":{"type":"integer","description":"Count of distinct user ids where atCap is true."}}}}},"CrossInstitutionChartRequest":{"type":"object","required":["institution"],"properties":{"institution":{"type":"string","description":"Space-separated institution IDs to compare","example":"60d5ec9af682fbd39c3a1234 60d5ec9af682fbd39c3a5678"},"granularity":{"type":"string","enum":["month","week","day","hour"],"default":"month"},"range":{"type":"number","description":"Number of time periods to look back (negative for past)","example":-12},"dimension":{"type":"string","enum":["count","credits","usage","cached","price","users"],"default":"count"},"includeTotal":{"type":"boolean","description":"Include a __total__ series summing all institutions per bucket","default":false}}},"CrossInstitutionChartResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"object","properties":{"granularity":{"type":"string"},"institution":{"type":"string"}}},"dimension":{"type":"number"}}}},"institutionNames":{"type":"object","additionalProperties":{"type":"string"},"description":"Map of institution ID to display name"}}},"UserSearchRequest":{"type":"object","properties":{"account":{"type":"string","description":"Account ID(s) to filter users (space-separated for multiple)"},"institution":{"type":"string","description":"Institution ID to filter users"},"accountType":{"type":"string","description":"Account type filter (user, admin, super)","enum":["user","admin","super"]},"usersearch":{"type":"string","description":"Search term for users (matches email, first name, last name)"},"activeOnly":{"type":"boolean","default":false,"description":"When true, restrict to users that have actually been used (lastActivityAt is set, not null)."},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-based)"},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":100,"description":"Number of results per page"},"limitsearch":{"type":"number","description":"DEPRECATED: Use pageSize instead. Maximum number of results to return","deprecated":true}}},"UserListResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Request success status"},"data":{"type":"array","items":{"$ref":"#/components/schemas/User"},"description":"Array of user objects"},"total":{"type":"integer","description":"Total number of matching users"},"hasMore":{"type":"boolean","description":"Whether more results are available beyond the current page"},"page":{"type":"integer","description":"Current page number"},"pageSize":{"type":"integer","description":"Number of results per page"},"message":{"type":"string","description":"Response message"}}},"UserData":{"type":"object","properties":{"rt_voice":{"type":"string","description":"Real-time voice setting","example":"coral"},"use_location":{"type":"boolean","description":"Whether user allows location usage"},"showSideBar":{"type":"boolean","description":"Show Sidebar in Pria UI"},"dark_mode":{"type":"boolean","description":"Dark mode preference"},"_id":{"type":"string","description":"User ID"},"email":{"type":"string","description":"User email address"},"fname":{"type":"string","description":"First name"},"lname":{"type":"string","description":"Last name"},"picture":{"type":"string","description":"Profile picture URL"},"password":{"type":"string","description":"Encrypted password hash"},"accountType":{"type":"string","description":"Type of user account"},"permissions":{"type":"array","items":{"type":"string"},"description":"User permissions array"},"customerId":{"type":"string","description":"Customer ID"},"lxp_user_id":{"type":"string","description":"LXP user identifier"},"lxp_user_type":{"type":"number","description":"LXP user type"},"lxp_partner_id":{"type":"string","description":"LXP partner ID"},"lxp_partner_name":{"type":"string","description":"LXP partner name"},"lxp_role_id":{"type":"number","description":"LXP role ID"},"lxp_role_name":{"type":"string","description":"LXP role name"},"credits":{"type":"number","description":"Available credits"},"creditsUsed":{"type":"number","description":"Credits used"},"plan":{"type":"string","description":"User subscription plan"},"status":{"type":"string","description":"User account status"},"trial_end":{"type":"string","format":"date-time","description":"Trial end date"},"trial_used":{"type":"boolean","description":"Whether trial has been used"},"current_period_end":{"type":"string","format":"date-time","description":"Current billing period end"},"cancel_at_period_end":{"type":"boolean","description":"Whether to cancel at period end"},"referralId":{"type":"string","description":"Referral ID"},"referrerPaid":{"type":"boolean","description":"Whether referrer has been paid"},"institution":{"$ref":"#/components/schemas/Institution"},"resetCodeId":{"type":"string","description":"Password reset code ID"},"direct":{"type":"boolean","description":"Direct user flag"},"invoices_urls":{"type":"array","items":{"type":"string"},"description":"Array of invoice URLs"},"remember_history_count":{"type":"number","description":"Number of history items to remember"},"browser_voice":{"type":"string","description":"Browser voice setting"},"created":{"type":"string","format":"date-time","description":"Account creation date"}}},"AdminUserResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Request success status"},"data":{"$ref":"#/components/schemas/UserData"}}},"CreateUserRequest":{"type":"object","required":["email","fname","lname","status","plan","accountType","password","institution"],"properties":{"email":{"type":"string","format":"email"},"fname":{"type":"string"},"lname":{"type":"string"},"status":{"type":"string","enum":["active","inactive"]},"plan":{"type":"string","enum":["free","sdk"]},"credits":{"type":"integer"},"accountType":{"type":"string","enum":["user","admin"]},"picture":{"type":"string","format":"uri"},"password":{"type":"string"},"institution":{"type":"string"},"remember_history_count":{"type":"integer"},"mustChangePassword":{"type":"boolean"}}},"CreateUserResponse":{"type":"object","properties":{"success":{"type":"boolean"},"id":{"type":"string"},"message":{"type":"string"}}},"LastConversation":{"type":"object","properties":{"course_id":{"type":"number","description":"Course ID of last conversation"},"course_name":{"type":"string","description":"Course name of last conversation"}}},"UserInstitution":{"type":"object","properties":{"lastConversation":{"$ref":"#/components/schemas/LastConversation","type":"object","properties":{"course_id":{"type":"number","description":"Last course ID"},"course_name":{"type":"string","description":"Last course name"}}},"digitaltwin":{"type":"boolean","description":"Whether digital twin is enabled"},"_id":{"type":"string","description":"User institution relationship ID"},"user":{"$ref":"#/components/schemas/User"},"institution":{"$ref":"#/components/schemas/Institution"},"entitlements":{"type":"array","items":{"type":"string"},"description":"User entitlements"},"accountType":{"type":"string","description":"Account type in this institution"},"status":{"type":"string","description":"User status in institution"},"creditAwarded":{"type":"number","description":"Credits awarded to user"},"created":{"type":"string","format":"date-time","description":"Creation timestamp"},"lastLogin":{"type":"string","format":"date-time","description":"Last login timestamp"},"lastKA":{"type":"string","format":"date-time","description":"Last keep-alive timestamp"}}},"UserInstitutionsResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Success status"},"message":{"type":"string","description":"Response message"},"data":{"type":"array","items":{"$ref":"#/components/schemas/UserInstitution"},"description":"Array of user institutions"}}},"DeleteUserResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful","example":true},"message":{"type":"string","description":"Success message","example":"User deleted successfully"}}},"UserInstitutionRequest":{"type":"object","properties":{"account":{"type":"string","description":"Account ID(s) to filter entitlements (space-separated for multiple)"},"institution":{"type":"string","description":"Institution ID to filter entitlements"},"accountType":{"type":"string","description":"Account type filter (user, admin)","enum":["user","admin"]},"usersearch":{"type":"string","description":"Search term for users (matches email, first name, last name)"},"emptyInstitutions":{"type":"boolean","description":"Whether to filter for users with no institution (super admin only)"},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-based)"},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":100,"description":"Number of results per page"},"limitsearch":{"type":"number","description":"DEPRECATED: Use pageSize instead","deprecated":true}}},"UserInstitutionResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the request was successful"},"data":{"type":"array","items":{"$ref":"#/components/schemas/UserInstitution"},"description":"Array of user institution relationships"},"total":{"type":"integer","description":"Total number of matching entitlements"},"hasMore":{"type":"boolean","description":"Whether more results are available"},"page":{"type":"integer","description":"Current page number"},"pageSize":{"type":"integer","description":"Number of results per page"},"message":{"type":"string","description":"Response message"}}},"UserInstitutionUpdateRequest":{"type":"object","properties":{"accountType":{"type":"string","description":"The account type to update for the user","example":"admin"},"entitlements":{"type":"array","items":{"type":"string"},"description":"Array of entitlement permissions for the user institution","example":["institutions.list","institutions.add","users.list","users.edit"]},"status":{"type":"string","description":"The account status, active, pending","example":"active"}}},"UserInstitutionUpdateResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful","example":true},"message":{"type":"string","description":"Success message","example":"User Institution updated!"}}},"SuccessError":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Invalid User Institution"}}},"EligibleUsersRequest":{"type":"object","required":["institution"],"properties":{"institution":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"Institution ID to find eligible users for","example":"67826dfe5d387207aabd6fc1"},"search":{"type":"string","description":"Optional search term to filter by name or email (2+ characters)","example":"john"}}},"EligibleUsersResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"array","description":"List of eligible users (max 100)","items":{"type":"object","properties":{"user":{"type":"object","properties":{"_id":{"type":"string","example":"67826dfe5d387207aabd6fc1"},"fname":{"type":"string","example":"John"},"lname":{"type":"string","example":"Doe"},"email":{"type":"string","example":"john@example.com"},"accountType":{"type":"string","example":"user"}}},"institutions":{"type":"array","description":"Institutions the user is currently a member of","items":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"}}}}}}},"total":{"type":"integer","description":"Total number of eligible users before capping","example":42}}},"EnrollUsersRequest":{"type":"object","required":["institution","users","accountType"],"properties":{"institution":{"type":"string","pattern":"^[0-9a-fA-F]{24}$","description":"Institution ID to enroll users into","example":"67826dfe5d387207aabd6fc1"},"users":{"type":"array","items":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"},"description":"Array of user IDs to enroll","example":["67826dfe5d387207aabd6fc1","67826dfe5d387207aabd6fc2"]},"accountType":{"type":"string","enum":["user","admin"],"description":"Role for the new memberships","example":"user"},"entitlements":{"type":"array","items":{"type":"string"},"description":"Admin entitlements to grant (only relevant when accountType is admin)","example":["institutions.list","users.list","users.edit"]}}},"EnrollUsersResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"created":{"type":"integer","description":"Number of users successfully enrolled","example":3},"skipped":{"type":"integer","description":"Number of users skipped (already enrolled or invalid)","example":1},"message":{"type":"string","description":"Human-readable result summary","example":"Enrolled 3 user(s). Skipped 1 (already enrolled or invalid)."}}},"AccountSearchRequest":{"type":"object","properties":{"accountsearch":{"type":"string","description":"Case-insensitive search term matched against account name and managerEmail (regex-escaped)"},"activeOnly":{"type":"boolean","default":false,"description":"When true, restrict to accounts that have actually been used (lastActivityAt is set, not null)."},"minimum":{"type":"boolean","description":"Whether to return minimal account data (bypasses pagination)"},"insitutionsUsage":{"type":"boolean","description":"When true, includes an institutionsUsage summary (count, credits, creditsUsed, creditsTotal, creditsUsagePct) aggregated across the account's institutions"},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-based)"},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":50,"description":"Number of results per page"}}},"AccountListResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Request success status"},"data":{"type":"array","items":{"type":"object"},"description":"Array of account objects"},"total":{"type":"integer","description":"Total number of matching accounts"},"hasMore":{"type":"boolean","description":"Whether more results are available"},"page":{"type":"integer","description":"Current page number"},"pageSize":{"type":"integer","description":"Number of results per page"},"message":{"type":"string","description":"Response message"}}},"SessionSearchRequest":{"type":"object","properties":{"account":{"type":"string","description":"Account ID(s) to filter sessions (space-separated for multiple)"},"institution":{"type":"string","description":"Institution ID to filter sessions"},"usersearch":{"type":"string","description":"Search term for users (matches email, first name, last name)"},"daterange":{"type":"array","items":{"type":"string","format":"date-time","nullable":true},"description":"[startDate, endDate] — defaults to last 30 days if not provided"},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-based)"},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":100,"description":"Number of results per page"},"limitsearch":{"type":"number","description":"DEPRECATED: Use pageSize instead","deprecated":true}}},"SessionListResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Request success status"},"data":{"type":"array","items":{"type":"object"},"description":"Array of session objects"},"total":{"type":"integer","description":"Total number of matching sessions"},"hasMore":{"type":"boolean","description":"Whether more results are available"},"page":{"type":"integer","description":"Current page number"},"pageSize":{"type":"integer","description":"Number of results per page"},"message":{"type":"string","description":"Response message"}}},"FeedbackSearchRequest":{"type":"object","properties":{"account":{"type":"string","description":"Account ID(s) to filter feedbacks (space-separated for multiple)"},"institution":{"type":"string","description":"Institution ID to filter feedbacks"},"usersearch":{"type":"string","description":"Search term for users (matches email, first name, last name)"},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-based)"},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":100,"description":"Number of results per page"}}},"FeedbackListResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Request success status"},"data":{"type":"array","items":{"type":"object"},"description":"Array of feedback objects"},"total":{"type":"integer","description":"Total number of matching feedbacks"},"hasMore":{"type":"boolean","description":"Whether more results are available"},"page":{"type":"integer","description":"Current page number"},"pageSize":{"type":"integer","description":"Number of results per page"},"message":{"type":"string","description":"Response message"}}},"PaymentSearchRequest":{"type":"object","properties":{"account":{"type":"string","description":"Account ID(s) to filter payments (space-separated for multiple)"},"institution":{"type":"string","description":"Institution ID to filter payments"},"usersearch":{"type":"string","description":"Search term for users (matches email, first name, last name)"},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-based)"},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":100,"description":"Number of results per page"},"limitsearch":{"type":"integer","deprecated":true,"description":"Legacy limit parameter (use pageSize instead)"}}},"PaymentListResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Request success status"},"data":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string","description":"Payment record ID"},"date":{"type":"string","format":"date-time","description":"Payment date"},"user":{"type":"object","description":"User who made the payment","properties":{"_id":{"type":"string"},"email":{"type":"string"},"fname":{"type":"string"},"lname":{"type":"string"}}},"institution":{"type":"object","description":"Institution associated with payment","nullable":true,"properties":{"_id":{"type":"string"},"name":{"type":"string"}}},"amount":{"type":"integer","description":"Payment amount in cents"},"currency":{"type":"string","description":"Currency code (e.g., \"usd\")"},"credits":{"type":"integer","description":"Credits purchased"},"plan":{"type":"string","description":"Plan name"},"source":{"type":"string","description":"Payment source"},"description":{"type":"string","description":"Payment description"},"invoiceUrl":{"type":"string","description":"Stripe invoice URL"}}},"description":"Array of payment objects"},"total":{"type":"integer","description":"Total number of matching payments"},"hasMore":{"type":"boolean","description":"Whether more results are available"},"page":{"type":"integer","description":"Current page number"},"pageSize":{"type":"integer","description":"Number of results per page"},"message":{"type":"string","description":"Response message"}}},"ToolCreateRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Unique tool name"},"description":{"type":"string","description":"Short user-facing description"},"instructions":{"type":"string","description":"Long-form instructions to the model on when/how to invoke this tool"},"argument_1":{"type":"string","description":"Natural-language description of the first argument"},"argument_2":{"type":"string"},"argument_3":{"type":"string"},"argument_4":{"type":"string"},"argument_5":{"type":"string"},"argument_6":{"type":"string"},"isArray":{"type":"boolean","default":false},"rtEnabled":{"type":"boolean","default":false},"rtOnly":{"type":"boolean","default":false},"categories":{"type":"array","items":{"type":"string","enum":["research","productivity","creative","communication","learning"]}},"status":{"type":"string","enum":["active","inactive","deleted"],"default":"active"}}},"ToolUpdateRequest":{"type":"object","description":"Partial update — only fields present in the body are written. The handler calls\n`Tool.updateOne({_id}, body)` with whatever is supplied; no field is required.\n","properties":{"name":{"type":"string"},"description":{"type":"string"},"instructions":{"type":"string"},"argument_1":{"type":"string"},"argument_2":{"type":"string"},"argument_3":{"type":"string"},"argument_4":{"type":"string"},"argument_5":{"type":"string"},"argument_6":{"type":"string"},"isArray":{"type":"boolean"},"rtEnabled":{"type":"boolean"},"rtOnly":{"type":"boolean"},"categories":{"type":"array","items":{"type":"string","enum":["research","productivity","creative","communication","learning"]}},"status":{"type":"string","enum":["active","inactive","deleted"]}}},"ToolListRequest":{"type":"object","properties":{"minimum":{"type":"boolean","description":"When true, returns a lightweight list (name, _id, status, description,\nrtEnabled, rtOnly, categories) with NO pagination. Used by RT dropdown\nselectors. Available to all authenticated users; full mode requires super admin.\n"},"institution":{"type":"string","description":"Minimum mode only. Institution to compute Google-service availability for —\nrows gain `unavailable`/`unavailableReason`/`locked` based on that\ninstitution's Google Cloud configuration (used by the instance editor's\nConnector MCP and Tools tab). Super admins may target any institution;\nother callers are limited to their own.\n"},"page":{"type":"integer","minimum":1,"default":1,"description":"Page number (1-based, ignored when minimum=true)"},"pageSize":{"type":"integer","minimum":1,"maximum":5000,"default":100,"description":"Number of results per page (ignored when minimum=true)"}}},"ToolListResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Tool"},"description":"Array of tools (sorted by name asc). Excludes `deleted` status; minimum mode also excludes `inactive`."},"total":{"type":"integer","description":"Total number of matching tools (omitted when minimum=true)"},"hasMore":{"type":"boolean","description":"Whether more pages remain (omitted when minimum=true)"},"page":{"type":"integer","description":"Current page number (omitted when minimum=true)"},"pageSize":{"type":"integer","description":"Page size used (omitted when minimum=true)"},"message":{"type":"string"}}},"ToolCreateResponse":{"type":"object","properties":{"success":{"type":"boolean"},"id":{"type":"string","description":"ObjectId of the newly created tool"},"message":{"type":"string"}}},"ToolUpdateResponse":{"type":"object","properties":{"success":{"type":"boolean"},"id":{"type":"string","description":"ObjectId of the updated tool"},"message":{"type":"string"}}},"ToolResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Tool"}}},"Upload":{"type":"object","description":"Admin view of an upload row. Mirrors the persisted shape from routes/models/upload.js, with two admin-handler shape changes: (1) thumbnail is returned as a base64-encoded string instead of a Buffer, and (2) an embeddings array of chunk text strings is appended when the embeddings DB connection is ready.","properties":{"_id":{"type":"string","description":"Upload ID (Mongo ObjectId)"},"filename":{"type":"string","description":"System-generated unique filename used as the on-disk identifier"},"originalname":{"type":"string","description":"Original filename as uploaded by the user (display name)"},"mimetype":{"type":"string","description":"MIME type of the file"},"thumbnail":{"type":"string","description":"Base64-encoded thumbnail (admin endpoint converts the underlying Buffer to base64 before returning)"},"created":{"type":"string","format":"date-time","description":"Upload creation timestamp"},"filesize":{"type":"integer","description":"File size in bytes"},"status":{"type":"string","enum":["inactive","selected","active","error","deleted"],"description":"Persisted upload state. inactive: waiting on RAG / ingestion queue (default). selected: RAG-ingested and included in the vault. active: uploaded with skipIndexing — visible but not RAG-indexed. error: RAG/ingestion failed. deleted: soft-delete tombstone."},"user":{"oneOf":[{"type":"string"},{"type":"object"}],"description":"User ID who uploaded the file. Populated on GET /api/admin/upload/{id} with { fname, lname, email }.","properties":{"_id":{"type":"string"},"email":{"type":"string","format":"email"},"fname":{"type":"string"},"lname":{"type":"string"}}},"institution":{"oneOf":[{"type":"string"},{"type":"object"}],"description":"Institution ID the file is shared with (null for personal files). Populated on GET /api/admin/upload/{id} with { name }.","properties":{"_id":{"type":"string"},"name":{"type":"string"}}},"account_shared":{"type":"boolean","description":"When true the file is shared across all institutions in the same account","default":false},"collection":{"type":"string","description":"Collection ID this upload belongs to (null when not in a collection)"},"file_summary":{"type":"string","description":"AI-generated summary of file content"},"file_title":{"type":"string","description":"AI-generated title for the file"},"file_authors":{"type":"string","description":"Authors of the content"},"file_url":{"type":"string","description":"URL to access the file"},"source_url":{"type":"string","description":"Original source URL the file was fetched from (when applicable)"},"embed_url":{"type":"string","description":"Embed URL for the file (when applicable)"},"file_dimensions":{"type":"string","description":"Image dimensions string (for image files)"},"tokens_used":{"type":"integer","description":"Number of tokens consumed processing this file (summarization + embeddings)","default":0},"is_public":{"type":"boolean","description":"When true the file is publicly accessible (anyone with the link, no auth)","default":false},"is_private":{"type":"boolean","description":"When true the file is confidential (owner-only access, suppressed from citations). Cannot coexist with is_public:true.","default":false},"embeddings_model":{"type":"string","description":"Model identifier used to generate the embeddings"},"summary_model":{"type":"string","description":"Model identifier used to generate the file summary"},"image_analysis_model":{"type":"string","description":"Model identifier used for image analysis"},"audio_analysis_model":{"type":"string","description":"Model identifier used for audio analysis"},"googleDriveFileId":{"type":"string","description":"Google Drive file ID when synced from Drive"},"googleDriveModifiedTime":{"type":"string","format":"date-time","description":"Last-modified timestamp from Google Drive"},"ragHitCount":{"type":"integer","description":"Count of times this file appeared in RAG search results (deduplicated per search)","default":0},"lastRagHitAt":{"type":"string","format":"date-time","nullable":true,"description":"Timestamp of most recent RAG search match"},"vaultHealthScore":{"type":"integer","nullable":true,"description":"Percentage (0-100) of chunks flagged as unoptimized content. null = not yet scanned."},"downloadCount":{"type":"integer","description":"Number of times the file has been downloaded","default":0},"lastDownloadAt":{"type":"string","format":"date-time","nullable":true,"description":"Timestamp of the most recent download"},"diskMissCount":{"type":"integer","description":"Strike counter incremented when res.sendFile errors on a selected upload (record exists but file missing from disk). Auto-tombstones after N strikes past the grace window — see routes/middlewares/uploadAuth.js.","default":0},"diskMissFirstAt":{"type":"string","format":"date-time","nullable":true,"description":"Timestamp of the first disk-miss strike in the current run"},"batchId":{"type":"string","nullable":true,"description":"Identifier of the upload batch this file was part of (set when uploaded as a multi-file batch)"},"history":{"type":"string","nullable":true,"description":"Back-reference to the upload-time History row (runFinalize updates it with final usage/credits/price)"},"replacedBy":{"type":"array","items":{"type":"string"},"description":"Upload IDs that supersede this row (set when the original was split into parts)"},"ingestion":{"type":"object","description":"Per-upload ingestion progress block written by routes/middlewares/ingestionWorker.js heartbeats","properties":{"phase":{"type":"string","enum":["queued","extract","chunk","sanitize","embed","finalize","done","error"],"nullable":true,"description":"Current ingestion phase"},"progress":{"type":"number","description":"0-1 progress fraction for the active phase","default":0},"attempts":{"type":"integer","description":"Retry attempts so far","default":0},"heartbeatAt":{"type":"string","format":"date-time","nullable":true,"description":"Last heartbeat timestamp from the ingestion worker"},"claimedBy":{"type":"string","nullable":true,"description":"Worker identifier currently processing this upload"},"lastError":{"type":"string","nullable":true,"description":"Most recent error message (capped at 2000 chars)"},"errorStack":{"type":"string","nullable":true,"description":"Most recent error stack trace (capped at 4500 chars)"},"errorClass":{"type":"string","enum":["permanent","transient","unknown","stalled","canceled","sanitize-threshold","sanitize-empty"],"nullable":true,"description":"Classification of the most recent error"},"enqueuedAt":{"type":"string","format":"date-time","nullable":true},"startedAt":{"type":"string","format":"date-time","nullable":true},"completedAt":{"type":"string","format":"date-time","nullable":true},"counts":{"type":"object","description":"Per-chunk progress snapshot","properties":{"total":{"type":"integer","default":0},"sanitized":{"type":"integer","default":0},"embedded":{"type":"integer","default":0}}},"sanitizeSkippedCount":{"type":"integer","description":"Number of chunks where chunkNeedsSanitize() returned false and the sanitize LLM call was skipped","default":0}}},"embeddings":{"type":"array","items":{"type":"string"},"description":"Admin-only: chunk text strings for every embedding row belonging to this upload, sorted by chunkIndex. Only attached when the embeddings DB connection is ready; omitted otherwise."}}},"UploadResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Upload"}}},"UserMergeRequest":{"type":"object","required":["sourceUserId","targetUserId"],"properties":{"sourceUserId":{"type":"string","description":"The user to merge FROM. Hard-deleted on a successful apply; must not be a super-admin."},"targetUserId":{"type":"string","description":"The user to merge INTO (survives). Every ref:'user' on the source is repointed here."},"dryRun":{"type":"boolean","default":false,"description":"When true, returns the preview (per-collection counts + conflicts) and writes nothing."},"confirm":{"type":"boolean","description":"Required (true) to APPLY a merge (when dryRun is not true). Forces dry-run-then-confirm."}}},"UserMergePreview":{"type":"object","properties":{"mode":{"type":"string","example":"merge"},"perCollectionCounts":{"type":"object","additionalProperties":{"type":"integer"},"description":"Count of the source's references per `model.path` that will be repointed."},"conflicts":{"type":"object","properties":{"overlappingInstitutions":{"type":"array","items":{"type":"string"},"description":"Institutions where both users have a membership (will be folded)."},"billingConflict":{"type":"boolean","description":"True when both users have distinct non-empty Stripe customerId (not reconciled)."}}}}},"UserMergeResponse":{"type":"object","properties":{"success":{"type":"boolean"},"mode":{"type":"string","enum":["merge","dryRun"]},"summary":{"type":"object","description":"On dryRun: the preview. On apply: { success, repointed, institutions, credits, auditId }."}}},"TrustedDevice":{"type":"object","properties":{"id":{"type":"string"},"deviceId":{"type":"string"},"sourceIp":{"type":"string"},"ua":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"}}},"MfaAuditEvent":{"type":"object","properties":{"id":{"type":"string"},"event":{"type":"string","example":"mfa.code.verified"},"user":{"type":"string","description":"Subject user ObjectId"},"actor":{"type":"string","description":"Who triggered the event (admin id for admin actions)"},"actorType":{"type":"string","enum":["self","admin","super","system"]},"ip":{"type":"string"},"ua":{"type":"string"},"meta":{"type":"object"},"created":{"type":"string","format":"date-time"}}},"ThreatIncidentSummary":{"type":"object","description":"Lightweight incident projection (heavy `evidence`/`llmAssessments` payloads stripped). Attacker-influenced text (`title`, `summary`, `categories`, `userEmail`, name fields) is returned verbatim and MUST be rendered INERT by the client (no HTML/markdown/auto-link).\n","properties":{"_id":{"type":"string"},"status":{"type":"string","enum":["open","reviewing","resolved","false_positive","escalated"]},"severity":{"type":"integer","minimum":0,"maximum":4},"categories":{"type":"array","items":{"type":"string"}},"title":{"type":"string"},"summary":{"type":"string"},"user":{"type":"string","description":"Flagged user id"},"userEmail":{"type":"string","description":"Attached from the user record (inert)"},"userFname":{"type":"string","description":"Attached from the user record (inert)"},"userLname":{"type":"string","description":"Attached from the user record (inert)"},"institutionIds":{"type":"array","items":{"type":"string"}},"institutions":{"type":"array","description":"Resolved institution names for the incident's institutionIds","items":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"}}}},"firstSeenAt":{"type":"string","format":"date-time"},"lastSeenAt":{"type":"string","format":"date-time"}}},"ThreatIncidentFull":{"type":"object","description":"Full incident document INCLUDING `evidence` and `llmAssessments`, plus resolved `institutions`/user name fields. Evidence and LLM-assessment text is attacker-influenced and MUST be rendered INERT by the client.\n","properties":{"_id":{"type":"string"},"status":{"type":"string","enum":["open","reviewing","resolved","false_positive","escalated"]},"severity":{"type":"integer","minimum":0,"maximum":4},"categories":{"type":"array","items":{"type":"string"}},"title":{"type":"string"},"summary":{"type":"string"},"user":{"type":"string"},"userEmail":{"type":"string"},"userFname":{"type":"string"},"userLname":{"type":"string"},"institutionIds":{"type":"array","items":{"type":"string"}},"institutions":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"}}}},"evidence":{"type":"array","description":"Evidence subdocuments (history references + extracted signals). Inert.","items":{"type":"object"}},"llmAssessments":{"type":"array","description":"Per-run LLM assessment records (rationale text). Inert.","items":{"type":"object"}},"reviewerNotes":{"type":"array","items":{"type":"object","properties":{"author":{"type":"string"},"note":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}}},"reviewerActions":{"type":"array","items":{"type":"object","properties":{"author":{"type":"string"},"action":{"type":"string"},"metadata":{"type":"object"},"createdAt":{"type":"string","format":"date-time"}}}},"firstSeenAt":{"type":"string","format":"date-time"},"lastSeenAt":{"type":"string","format":"date-time"}}}}},"tags":[{"name":"Authentication","description":"User authentication, registration, and password management (/api/auth)"},{"name":"OAuth","description":"OAuth authentication providers - Google, GitHub, SSO (/api/auth/oauth)"},{"name":"User","description":"User profile management and account operations (/api/user)"},{"name":"User Institutions","description":"User institution memberships and switching (/api/user/institution)"},{"name":"User Tools","description":"Available tools for authenticated users (/api/user/tools)"},{"name":"Institutions","description":"Institution settings and configuration (/api/user/institution)"},{"name":"Conversation","description":"AI conversation and Q&A endpoints (/api/ai)"},{"name":"Realtime","description":"Real-time voice AI and WebRTC sessions (/api/ai/rt)"},{"name":"Assistant","description":"AI assistant configuration and management (/api/user/assistant)"},{"name":"History","description":"Conversation history and favorites (/api/user/history)"},{"name":"RAG","description":"Document upload, embedding, and retrieval-augmented generation (/api/user/files, /api/user/rag)"},{"name":"Setting","description":"Instance variables and settings management (/api/user/setting)"},{"name":"Branding","description":"Digital twin branding and customization (/api/agent/branding)"},{"name":"Agent","description":"Agent engagement and session management (/api/agent)"},{"name":"SDK Launch","description":"SDK launch token signing and verification for secure iframe embedding (/api/auth/sdk-sign, /api/auth/sdk-verify)"},{"name":"Testing","description":"Health checks, diagnostics, and test endpoints (/api/test)"},{"name":"Admin Accounts","description":"Account management for super admins (/api/admin/account)"},{"name":"Admin Institutions","description":"Institution management for admins (/api/admin/institution)"},{"name":"Admin Users","description":"User management for admins (/api/admin/user)"},{"name":"Admin Entitlements","description":"User-institution relationships and permissions (/api/admin/userInstitution)"},{"name":"Admin Sessions","description":"Session management for admins (/api/admin/session)"},{"name":"Admin Histories","description":"Conversation history management and analytics (/api/admin/history)"},{"name":"Admin Assistants","description":"AI assistant management for admins (/api/admin/assistant)"},{"name":"Admin Questions","description":"Institution question and prompt management (/api/admin/question)"},{"name":"Admin Tools","description":"Tool configuration management (/api/admin/tool)"},{"name":"Admin AI Models","description":"AI model configuration (/api/admin/aimodel)"},{"name":"Admin MCP Servers","description":"Model Context Protocol server management (/api/admin/mcpserver)"},{"name":"Admin Feedbacks","description":"User feedback management (/api/admin/feedback)"},{"name":"Admin Uploads","description":"Upload management (/api/admin/upload)"},{"name":"Admin Charts","description":"Analytics and visualization chart management (/api/admin/chart)"},{"name":"Admin Memory","description":"Admin inspection and editing of user/instance memory parameters."},{"name":"Admin Usage Limits","description":"Per-user usage-vs-cap reporting and account-wide at-limit counts."}]}