{"openapi":"3.0.0","info":{"title":"Platform API","description":"Ethora Platform API.\n\n## Getting Started\n- For new backend and tenant integrations, start with the `/v2/...` endpoints.\n- Use `/v2/apps/{appId}/...` when your backend/admin flow manages a specific target app explicitly.\n- Some legacy operations still exist only under `/v1/...`; when so, the operation description will say so.\n\n## Integration Tracks\n- `Frontend / app runtime`: app-auth login/sign-up flows, then user-auth chats/files/sharelinks.\n- `Tenant admin / B2B backend`: explicit app management and app-scoped admin flows under `/v2/apps/{appId}/...`.\n\n## Auth Token Legend\n- `App JWT`: app-scoped runtime token for sign-up/login/runtime app operations.\n- `User JWT`: end-user or admin-user session token.\n- `Refresh JWT`: refresh-only token for access token renewal.\n- `Client JWT`: legacy client-side token used only for chat/client credential exchange compatibility flows.\n- `B2B Server JWT`: tenant/backend integration token. Prefer `Authorization: Bearer <JWT>`; `x-custom-token` is still accepted for backward compatibility.\n\n## Which Token Where\n- `App JWT`: `/v1/users/login-with-email`, `/v2/users/sign-up-with-email`, similar app-runtime auth endpoints.\n- `User JWT`: user session endpoints like chats, files, sharelinks, profile/session management.\n- `B2B Server JWT`: tenant/backend integration endpoints, especially explicit `/v2/apps/{appId}/...` admin flows.\n- `Client JWT`: only where the endpoint explicitly says `Client JWT` and typically only for legacy compatibility.\n\n## Auth Approach\n- `/v2/...` endpoints are the canonical API surface for new integrations.\n- Endpoints may support one or more token types depending on the actor and scope.\n- App-scoped admin/integration endpoints use `/v2/apps/{appId}/...` so the target app is always explicit.\n- When an endpoint supports both `User JWT` and `B2B Server JWT`, Swagger will show both under Security.\n- Public Swagger excludes internal-only, operational, and experimental surfaces.","contact":{"name":"Dappros developers team"},"version":"26.10.14"},"servers":[{"url":"https://api.chat.ethora.com"}],"tags":[{"name":"Auth","description":"Authentication and session management."},{"name":"Users","description":"Managing Users. <br />ℹ️ Users are end user identities that have authenticated with one of the allowed sign on methods. <br />Each user receives an EVM (Ethereum) compatible \"L2\" cryptographic identity or wallet by default and additional wallets/IDs can be linked if required."},{"name":"Chats","description":"Chat endpoints (rooms, members, messages helpers) complementing the primary XMPP chat flow."},{"name":"Files","description":"Managing user uploaded files. ℹ️ Used for chat attachments, documents, user profiles, room media, digital collectibles, etc."},{"name":"Sharelinks","description":"Secure sharing links for resources such as User Profiles and Documents. <br />ℹ️ Used when resource is not public and/or encrypted."},{"name":"Apps","description":"Application and tenant-admin management. <br />ℹ️ Use `/v2/apps` and `/v2/apps/{appId}/...` for explicit backend/admin integrations."},{"name":"AI Widget","description":"AI Widget integration endpoints. <br />ℹ️ The widget is the embeddable chat experience served by `assistant.js`; visitors are anonymous (XMPP-only Mongo Users tagged `isVisitor:true`), conversations are persistent MUC rooms tagged `chats.type='widget'`, and the AI bot is the App's configured agent."},{"name":"Agents","description":"AI Agents and their BotInstances. <br />ℹ️ An Agent is an account-level AI persona (prompt, model, Memory, knowledge, flows), embodied in an App as a BotInstance the first time it is invited into one of that App's chats. Platform-owned agents such as the Support Agent template are read-only to tenants, who clone them."}],"components":{"securitySchemes":{"JwtAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Authorization: Bearer <JWT>"},"AppJwtAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"App JWT (data.type=\"app\"). Use this token to call app-authorized endpoints (e.g. create user, login, signup). Authorization: Bearer <JWT>. Wrong token type returns code=INVALID_TOKEN_TYPE."},"UserJwtAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"User JWT (data.type=\"user\"). Use this token for user-authorized endpoints (e.g. chats/files/sharelinks/me). Authorization: Bearer <JWT>. Wrong token type returns code=INVALID_TOKEN_TYPE."},"RefreshJwtAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Refresh JWT (data.type=\"refresh\"). Use this token only for refresh flow. Authorization: Bearer <JWT>. Wrong token type returns code=INVALID_TOKEN_TYPE."},"ClientJwtAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Client JWT (data.type=\"client\"). Legacy compatibility token used only for client credential exchange endpoints. Preferred header: x-custom-token; Authorization Bearer may also be accepted by some deployments."},"B2BServerJwtAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"B2B Server JWT (data.type=\"server\"). Use this token for tenant/backend integration endpoints. Preferred header: Authorization: Bearer <JWT>. Legacy header x-custom-token is also accepted for backward compatibility."}},"schemas":{"DefaultRooms":{"type":"object","properties":{"jid":{"type":"string"},"pinned":{"type":"boolean"}}},"UserData":{"type":"object","properties":{"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"}}},"UserDataB2B":{"type":"object","properties":{"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"password":{"type":"string"},"uuid":{"type":"string"},"profileImage":{"type":"string","description":"Optional profile image URL."},"profileImageFileIndex":{"type":"integer","description":"Multipart-only: index into uploaded files[] to be uploaded and used as profileImage."}}},"UserNormalLogin":{"type":"object","required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string"}}},"UserEmailLogin":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string"},"password":{"type":"string"}}},"UserGoogleAppleFacebookLogin":{"type":"object","required":["loginType","password","authToken"],"properties":{"loginType":{"type":"string","enum":["google","facebook","apple"]},"authToken":{"type":"string"},"signupPlan":{"type":"string","enum":["business","free"]},"utm":{"type":"string"}}},"SignatureLogin":{"type":"object","required":["walletAddress","signature","msg"],"properties":{"walletAddress":{"type":"string"},"signature":{"type":"string"},"msg":{"type":"string"}}},"SignatureRegistration":{"type":"object","required":["walletAddress","signature","msg","firstName","lastName"],"properties":{"walletAddress":{"type":"string"},"signature":{"type":"string"},"msg":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"utm":{"type":"string"}}},"UsernameRegistration":{"type":"object","required":["username","password","firstName","lastName"],"properties":{"username":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"}}},"EmailRegistration":{"type":"object","required":["email","password","firstName","lastName"],"properties":{"email":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"}}},"UserLoginSuccess":{"type":"object","properties":{"success":{"type":"boolean"},"msg":{"type":"string","default":"success"},"token":{"type":"string"},"success_xmpp":{"type":"boolean"}}},"UserRegistrationResponse":{"type":"object","properties":{"success":{"type":"boolean"},"msg":{"type":"string"},"token":{"type":"string"},"refreshToken":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Always false for error responses.","example":false},"error":{"type":"string","description":"A brief, human-readable message about the error."},"code":{"type":"string","description":"A stable machine-readable error code.","example":"VALIDATION_ERROR"},"details":{"type":"object","description":"Optional, more specific details about the error (e.g., validation errors)."},"errors":{"description":"Legacy validation errors (if applicable).","nullable":true},"requestId":{"type":"string","description":"Request correlation id for debugging/support."}},"required":["error"],"example":{"success":false,"error":"Validation Failed","code":"VALIDATION_ERROR","details":{"field":"Reason"},"requestId":"..."}},"UserPublic":{"type":"object","properties":{"_id":{"type":"string"},"uuid":{"type":"string","nullable":true},"xmppUsername":{"type":"string"},"email":{"type":"string","nullable":true},"firstName":{"type":"string","nullable":true},"lastName":{"type":"string","nullable":true},"username":{"type":"string","nullable":true},"profileImage":{"type":"string","nullable":true},"profilePicture":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"tags":{"type":"array","items":{"type":"string"},"nullable":true}},"additionalProperties":true},"AuthTokens":{"type":"object","properties":{"token":{"type":"string","description":"Access token (JWT / Bearer)."},"refreshToken":{"type":"string"},"wsToken":{"type":"string","nullable":true}},"required":["token","refreshToken"]},"LoginSuccessResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"token":{"type":"string"},"refreshToken":{"type":"string"},"wsToken":{"type":"string"},"user":{"$ref":"#/components/schemas/UserPublic"},"isAllowedNewAppCreate":{"type":"boolean","nullable":true}},"required":["success","token","refreshToken","user"],"additionalProperties":true},"MeResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"token":{"type":"string"},"refreshToken":{"type":"string"},"wsToken":{"type":"string","nullable":true},"user":{"$ref":"#/components/schemas/UserPublic"},"app":{"type":"object","properties":{"isUserDataEncrypted":{"type":"boolean","nullable":true}},"additionalProperties":true},"isAllowedNewAppCreate":{"type":"boolean","nullable":true}},"required":["success","token","refreshToken","user"],"additionalProperties":true},"FileRecord":{"type":"object","properties":{"_id":{"type":"string"},"userId":{"type":"string"},"ownerKey":{"type":"string"},"location":{"type":"string"},"locationPreview":{"type":"string","nullable":true},"duration":{"type":"string","nullable":true},"originalname":{"type":"string"},"filename":{"type":"string"},"mimetype":{"type":"string"},"size":{"type":"number"},"expiresAt":{"type":"string","nullable":true},"isPrivate":{"type":"boolean","nullable":true},"createdAt":{"type":"string","nullable":true},"updatedAt":{"type":"string","nullable":true}},"additionalProperties":true},"FilesUploadResponse":{"type":"object","properties":{"success":{"type":"boolean"},"results":{"type":"array","items":{"$ref":"#/components/schemas/FileRecord"}}},"required":["success","results"]},"FilesListResponse":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"},"items":{"type":"array","items":{"$ref":"#/components/schemas/FileRecord"}}},"required":["limit","offset","total","items"]},"ChatRecord":{"type":"object","properties":{"_id":{"type":"string","nullable":true},"name":{"type":"string"},"title":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"type":{"type":"string","nullable":true},"picture":{"type":"string","nullable":true},"appId":{"type":"string","nullable":true},"createdBy":{"type":"string","nullable":true},"e2ee":{"type":"boolean","nullable":true},"reported":{"type":"boolean","nullable":true},"members":{"type":"array","items":{"$ref":"#/components/schemas/UserPublic"},"nullable":true},"createdAt":{"type":"string","nullable":true},"updatedAt":{"type":"string","nullable":true}},"additionalProperties":true},"ChatCreateResponse":{"type":"object","properties":{"result":{"$ref":"#/components/schemas/ChatRecord"}},"required":["result"]},"ChatListResponse":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/ChatRecord"}}},"required":["results"]},"ChatMyRoomsResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ChatRecord"}}},"required":["items"]},"ChatMyRoomResponse":{"type":"object","properties":{"result":{"$ref":"#/components/schemas/ChatRecord"}},"required":["result"]},"ChatUserChatsResponse":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/ChatRecord"}},"items":{"type":"array","items":{"$ref":"#/components/schemas/ChatRecord"}},"pagination":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}},"additionalProperties":true},"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}},"required":["results","items","pagination","limit","offset","total"]},"ChatUsersAccessDetail":{"type":"object","properties":{"xmppUsername":{"type":"string"},"status":{"type":"string","description":"Per-member status.","example":"added"},"code":{"type":"string","nullable":true,"description":"Optional machine-readable code for non-success statuses."}},"additionalProperties":true},"BatchSummary":{"type":"object","properties":{"requested":{"type":"integer","description":"How many items the client requested to process."},"byStatus":{"type":"object","additionalProperties":{"type":"integer"}}},"required":["requested","byStatus"],"additionalProperties":true},"ChatUsersAccessAddResponse":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"summary":{"$ref":"#/components/schemas/BatchSummary"},"results":{"type":"array","items":{"$ref":"#/components/schemas/UserPublic"}},"details":{"type":"array","items":{"$ref":"#/components/schemas/ChatUsersAccessDetail"}}},"required":["ok","summary","results","details"],"additionalProperties":false},"ChatUsersAccessRemoveResponse":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"summary":{"$ref":"#/components/schemas/BatchSummary"},"results":{"type":"array","items":{"type":"string"}},"details":{"type":"array","items":{"$ref":"#/components/schemas/ChatUsersAccessDetail"}}},"required":["ok","summary","results","details"],"additionalProperties":false},"ChatUsersPatchBatchDetail":{"type":"object","properties":{"uuid":{"type":"string","nullable":true},"xmppUsername":{"type":"string","nullable":true},"status":{"type":"string","example":"updated"},"reason":{"type":"string","nullable":true},"user":{"$ref":"#/components/schemas/UserPublic","nullable":true}},"additionalProperties":true},"ChatUsersPatchBatchResponse":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"summary":{"$ref":"#/components/schemas/BatchSummary"},"results":{"type":"array","items":{"$ref":"#/components/schemas/ChatUsersPatchBatchDetail"}},"details":{"type":"array","items":{"$ref":"#/components/schemas/ChatUsersPatchBatchDetail"}}},"required":["ok","summary","results","details"],"additionalProperties":false},"SourcesDeleteSiteV2Detail":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string","nullable":true},"deleted":{"type":"boolean"},"bytesDeleted":{"type":"integer"},"code":{"type":"string","example":"DELETED"}},"additionalProperties":true},"SourcesDeleteSiteV2Response":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"summary":{"type":"object","properties":{"requested":{"type":"integer"},"deleted":{"type":"integer"},"notFound":{"type":"integer"},"totalBytesDeleted":{"type":"integer"}},"required":["requested","deleted","notFound","totalBytesDeleted"],"additionalProperties":true},"totalSizeDeleted":{"type":"integer"},"details":{"type":"array","items":{"$ref":"#/components/schemas/SourcesDeleteSiteV2Detail"}},"result":{"type":"object","additionalProperties":true}},"required":["ok","summary","details"],"additionalProperties":true},"SourcesDeleteSiteV1Response":{"type":"object","properties":{"result":{"type":"string","description":"URL"},"deleted":{"type":"boolean"},"bytesDeleted":{"type":"integer"}},"required":["result","deleted","bytesDeleted"],"additionalProperties":true},"SourcesSiteCrawlResponse":{"type":"object","description":"Site crawl response. Shape may evolve; treat as semi-structured.","additionalProperties":true},"SourcesSiteCrawlJobResponse":{"type":"object","properties":{"result":{"type":"object","properties":{"jobId":{"type":"string"},"status":{"type":"string","enum":["queued","running","completed","failed"]},"kind":{"type":"string","enum":["crawl","reindex"]},"url":{"type":"string"},"followLink":{"type":"boolean"},"unlimited":{"type":"boolean"},"savedPages":{"type":"integer"},"receivedPages":{"type":"integer"},"totalBytes":{"type":"integer"},"truncated":{"type":"boolean"},"truncatedReason":{"type":"string","nullable":true},"error":{"type":"string","nullable":true},"startedAt":{"type":"string","nullable":true},"finishedAt":{"type":"string","nullable":true}},"additionalProperties":true}},"required":["result"],"additionalProperties":true},"SourcesSiteReindexResponse":{"type":"object","description":"Site reindex response. Shape may evolve; treat as semi-structured.","additionalProperties":true},"SourcesDocsUploadResponse":{"type":"object","properties":{"result":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"createdAt":{"type":"string","nullable":true},"url":{"type":"string","nullable":true},"mdByteSize":{"type":"integer"}},"additionalProperties":true}}},"required":["result"],"additionalProperties":true},"SourcesDocsDeleteResponse":{"type":"object","properties":{"result":{"type":"object","properties":{"id":{"type":"string"},"originalName":{"type":"string","nullable":true},"deleted":{"type":"boolean","nullable":true}},"additionalProperties":true}},"required":["result"],"additionalProperties":true},"AppTokenMeta":{"type":"object","properties":{"tokenId":{"type":"string"},"label":{"type":"string","nullable":true},"createdAt":{"type":"string","nullable":true},"lastUsedAt":{"type":"string","nullable":true},"revokedAt":{"type":"string","nullable":true},"active":{"type":"boolean"}},"additionalProperties":true},"AppTokensListResponse":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"appId":{"type":"string"},"tokens":{"type":"array","items":{"$ref":"#/components/schemas/AppTokenMeta"}}},"required":["ok","appId","tokens"],"additionalProperties":true},"AppTokenCreateResponse":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"appId":{"type":"string"},"tokenId":{"type":"string"},"token":{"type":"string","description":"Returned once. Store securely."},"label":{"type":"string","nullable":true},"createdAt":{"type":"string","nullable":true}},"required":["ok","appId","tokenId","token"],"additionalProperties":true},"AppTokenRevokeResponse":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"appId":{"type":"string"},"tokenId":{"type":"string"},"revoked":{"type":"boolean"},"status":{"type":"string","nullable":true}},"required":["ok","appId","tokenId","revoked"],"additionalProperties":true},"AppTokenRotateResponse":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"appId":{"type":"string"},"revokedTokenId":{"type":"string"},"tokenId":{"type":"string"},"token":{"type":"string","description":"Returned once. Store securely."},"label":{"type":"string","nullable":true},"createdAt":{"type":"string","nullable":true}},"required":["ok","appId","tokenId","token"],"additionalProperties":true},"AppProvisionRoomDetail":{"type":"object","properties":{"title":{"type":"string","nullable":true},"pinned":{"type":"boolean"},"status":{"type":"string","example":"created"},"code":{"type":"string","nullable":true},"error":{"type":"string","nullable":true},"room":{"type":"object","nullable":true,"additionalProperties":true}},"additionalProperties":true},"AppProvisionResponse":{"type":"object","properties":{"ok":{"type":"boolean","example":true},"appId":{"type":"string"},"summary":{"$ref":"#/components/schemas/BatchSummary"},"details":{"type":"array","items":{"$ref":"#/components/schemas/AppProvisionRoomDetail"}}},"required":["ok","appId","summary","details"],"additionalProperties":true},"PublicChatsResponse":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ChatRecord"}}},"required":["limit","offset","total","items"]},"SharelinkRecord":{"type":"object","properties":{"userId":{"type":"string"},"walletAddress":{"type":"string"},"expiration":{"type":"number"},"memo":{"type":"string","nullable":true},"resource":{"type":"string"},"token":{"type":"string"},"targetRecordId":{"type":"string","nullable":true}},"additionalProperties":true},"SharelinkCreateResponse":{"type":"object","properties":{"sharelinkData":{"$ref":"#/components/schemas/SharelinkRecord"}},"required":["sharelinkData"]},"SharelinkListResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SharelinkRecord"}}},"required":["items"]},"DefaultRoom":{"type":"object","properties":{"jid":{"type":"string"},"pinned":{"type":"boolean"}},"required":["jid"],"additionalProperties":true}},"responses":{"BadRequest":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Forbidden":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"UnprocessableEntity":{"description":"Unprocessable Entity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"InternalServerError":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":{"Limit":{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"description":"Max number of items to return."},"Offset":{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Number of items to skip before starting to collect the result set."}}},"security":[{"JwtAuth":[]}],"paths":{"/v1/push/firebase-service-account/{appId}":{"post":{"summary":"Upload Firebase service account for app push","description":"Uploads a Firebase service account JSON for the specified app. The backend proxies it to the configured push service.\n","tags":["Push"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["firebaseServiceAccount"],"properties":{"firebaseServiceAccount":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"forbidden"},"404":{"description":"app not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"post_v1_push_firebase_service_account_appid"},"delete":{"summary":"Delete the Firebase service account for app push","description":"Removes the app's Firebase service account from the push service and\nclears the `firebaseServiceAccountUploaded` flag on the app. Idempotent:\nsucceeds even if no service account was uploaded. Device subscriptions\nare left intact.\n","tags":["Push"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"forbidden"},"404":{"description":"app not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"delete_v1_push_firebase_service_account_appid"}},"/v1/push/apns/{appId}":{"post":{"summary":"Upload the APNs auth key (.p8) for app push","description":"Uploads an APNs auth key for direct APNs delivery (e.g. VoIP/PushKit\npushes, which FCM cannot deliver). The backend proxies it to the push\nservice, which stores it server-side — the key is never exposed back.\nSets `apnsKeyUploaded: true` on the app. `bundleId` defaults to the\napp's `bundleId` when omitted.\n","tags":["Push"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["apnsKey","keyId","teamId","environment"],"properties":{"apnsKey":{"type":"string","format":"binary","description":"The APNs auth key file (.p8, PEM private key)"},"keyId":{"type":"string","description":"Apple Key ID of the .p8 key (10 characters)"},"teamId":{"type":"string","description":"Apple Developer Team ID (10 characters)"},"bundleId":{"type":"string","description":"Target app bundle id (the apns-topic). May be omitted only\nwhen the app record has `bundleId` set - the resolved value\nis required, otherwise 422 BUNDLE_ID_REQUIRED.\n"},"environment":{"type":"string","enum":["production","sandbox"],"description":"APNs environment the key will be used against"}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"forbidden"},"404":{"description":"app not found"},"422":{"description":"validation failed (missing/invalid keyId, teamId, environment, bundleId, or .p8 file)"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"post_v1_push_apns_appid"},"delete":{"summary":"Delete the APNs auth key for app push","description":"Removes the app's APNs auth key from the push service and clears the\n`apnsKeyUploaded` flag on the app. Idempotent: succeeds even if no key\nwas uploaded.\n","tags":["Push"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"forbidden"},"404":{"description":"app not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"delete_v1_push_apns_appid"}},"/v1/push/subscription/{appId}":{"post":{"summary":"Register a device push token for the current user","description":"Creates or updates a push subscription for the current authenticated user (jid = req.user.xmppUsername).\nProxies to the configured push service.\n","tags":["Push"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["registrationToken","deviceType"],"properties":{"registrationToken":{"type":"string","description":"FCM registration token, or a raw APNs/PushKit device token when tokenType is apns/apns-voip"},"tokenType":{"type":"string","enum":["fcm","apns","apns-voip"],"description":"'fcm' (default) - Firebase registration token, delivered via FCM.\n'apns' - raw APNs device token, delivered via direct APNs\n(requires the app's APNs auth key to be uploaded).\n'apns-voip' - PushKit VoIP token; registered alongside the\ndevice's regular 'apns' subscription and used ONLY for\nincoming-call pushes (apns-push-type voip, topic\nbundleId.voip). Regular messages never go to it.\n"},"buildOrigin":{"type":"string","enum":["tenant","platform"],"description":"Which build family issued the token — delivery keys must\nmatch the BUILD that produced it, not the tenant.\n'tenant' (default) - the tenant's own white-label build;\ndelivered with the keys uploaded for this app.\n'platform' - the universal store app; delivered with the\nplatform's keys regardless of tenant.\n"},"deviceType":{"type":"string","enum":["android","ios","web"]},"pushKey":{"type":"string","description":"Encrypted pushes: base64 of the device's raw 32-byte X25519\npublic key. Chat pushes to this token then arrive data-only\nwith their content in a libsodium sealed box for this key\n(see the push service README, \"Encrypted pushes\"). An empty\nstring removes a stored key; omitting the field keeps it.\n"},"pushKeyAlg":{"type":"string","enum":["sealed-box-x25519"],"description":"Construction the key is for (default sealed-box-x25519)."},"pushKeyId":{"type":"string","description":"Key id returned in every sealed push as `kid`. Default: the\nfirst 16 hex characters of SHA-256 over the raw key.\n"}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"validation error"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"post_v1_push_subscription_appid"},"delete":{"summary":"Remove a device push token for the current user","description":"Deletes a push subscription for the current authenticated user (jid = req.user.xmppUsername).\nProxies to the configured push service.\n","tags":["Push"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["registrationToken"],"properties":{"registrationToken":{"type":"string"}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"validation error"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"delete_v1_push_subscription_appid"}},"/v1/push/platform/{appId}":{"put":{"summary":"Enable/disable platform-key push delivery for an app","description":"Push keys uploaded to the base app serve every app of the install, with\nno limits and no settings: an app without keys of its own sends with\nthe base app's keys, an app that uploads its own keys sends with those,\nand parent/child relations between apps play no role. This switch only\nturns platform-key delivery off for one app (enabled: false) or back\non; it can also be set when the app is created (`platformPushEnabled`\non POST /v1/apps and /v2/apps). Accepts a user token with\nappSettings.update permission on the target app, or a server (B2B)\ntoken of that app or of its parent app.\n","tags":["Push"],"parameters":[{"in":"path","name":"appId","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"ok ({ ok, enabled })"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"validation error"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"put_v1_push_platform_appid"},"get":{"summary":"Platform push status for an app","description":"Returns { enabled, platformProjectId } for the app's platform-key delivery (platformProjectId is the app whose keys serve it, normally the base app).","tags":["Push"],"parameters":[{"in":"path","name":"appId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"ok ({ enabled, platformProjectId })"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"get_v1_push_platform_appid"}},"/v1/push/project/{appId}":{"post":{"summary":"Send a broadcast push for an app (project)","description":"Proxies a broadcast push request to the configured push service.","tags":["Push"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string"},"title":{"type":"string"},"ttl":{"type":"integer","minimum":0},"jid":{"type":"string","description":"Optional JID to target a specific segment/user group (push service dependent)"}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"validation error"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"post_v1_push_project_appid"}},"/v1/push/validate/{appId}":{"post":{"summary":"Validate push configuration for an app (dry-run)","description":"Calls the push service to perform a **dry-run** Firebase send to a topic.\nThis validates that the Firebase service account for the app is configured and usable,\nwithout requiring a mobile app/device token.\n","tags":["Push"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","schema":{"type":"string"},"required":true}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"topic":{"type":"string","description":"Optional FCM topic to use for dry-run validation."}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"app/project not found"},"422":{"description":"validation error"},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"description":"FCM validation failed (bad service account / permissions)"}},"operationId":"post_v1_push_validate_appid"}},"/v1/push/user/{appId}":{"post":{"summary":"Send a push to a single user (jid)","description":"Proxies a single-user push request to the configured push service.","tags":["Push"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jid","text"],"properties":{"jid":{"type":"string"},"text":{"type":"string"},"title":{"type":"string"},"ttl":{"type":"integer","minimum":0}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"validation error"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"post_v1_push_user_appid"}},"/v2/apps":{"get":{"summary":"List apps available to the current tenant actor (v2)","description":"**Auth**: Tenant Actor\n\nLists apps visible to the current authenticated tenant actor.\nSupports `User JWT` (admin panel / MCP server) and `B2B Server JWT` (backend integrations).\n","tags":["Apps"],"operationId":"v2_apps_list","parameters":[{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","default":10,"minimum":1,"maximum":500}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","default":0,"minimum":0}},{"in":"query","name":"order","description":"Sort direction: `asc` (default) or `desc`.\n","schema":{"type":"string","enum":["asc","desc"],"default":"asc"}},{"in":"query","name":"orderBy","description":"Field to sort by. Allowed values are listed in the schema; if\nomitted, the endpoint's default ordering is used.\n","schema":{"type":"string","enum":["displayName","createdAt"],"default":"displayName"}},{"in":"query","name":"includeArchived","schema":{"type":"boolean","default":false},"description":"When `true`, the list includes archived apps alongside active ones\n(admin \"All apps\" view). Default excludes archived for safety.\n"},{"in":"query","name":"status","schema":{"type":"string","enum":["archived"]},"description":"When set to `archived`, returns ONLY archived apps (admin \"Restore\" view).\nMutually exclusive with the default active-only view; takes precedence over `includeArchived`.\n"}],"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]},"post":{"summary":"Create a child app for the current tenant actor (v2)","description":"**Auth**: Tenant Actor\n\nCreates an app under the current tenant / parent app context.\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Apps"],"operationId":"v2_apps_create","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["displayName"],"properties":{"displayName":{"description":"Human-readable app name shown in the admin panel, sign-up\nscreens, and chat headers. 3-100 chars.\n","type":"string"},"domainName":{"description":"Subdomain used for the hosted app at\n`<domainName>.<rootDomain>`. Alphanumeric only (no\ndashes); must be unique across the platform.\n","type":"string"},"appTagline":{"description":"Short marketing line shown beneath the display name on\nthe sign-up screen. Free-form.\n","type":"string"},"logoImage":{"description":"Main app logo. POST/PUT accept either a file upload\n(multipart) or a URL (JSON). 1:1 ratio recommended,\ndisplayed at 120x120 px in the admin panel.\n","type":"string"},"sublogoImage":{"description":"Optional secondary logo / favicon. Same input shapes as\n`logoImage`.\n","type":"string"},"primaryColor":{"description":"Brand colour as a 6-digit hex string (e.g. `#0052CD`).\nUsed as the accent throughout the chat UI.\n","type":"string"},"bundleId":{"description":"iOS/Android bundle identifier in reverse-DNS form (e.g.\n`com.acme.chat`). Required for native push notifications.\n","type":"string"},"createDefaultChat":{"type":"boolean","default":false,"description":"If true, the app will be seeded with a \"Main chat\"\ndefault chat room available to all users. Used when\ncreating new apps manually from within admin panel.\n"}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v2/apps/{appId}":{"get":{"summary":"Get a specific app visible to the current tenant actor (v2)","description":"**Auth**: Tenant Actor\n\nReturns a single app by id.\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Apps"],"operationId":"v2_apps_get_single","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"app not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]},"patch":{"summary":"Update a specific app visible to the current tenant actor (v2)","description":"**Auth**: Tenant Actor\n\nPartially updates an app by id.\nSupports `User JWT` and `B2B Server JWT`.\n`appDescription` is accepted as a compatibility alias for the legacy stored `appTagline` field.\n","tags":["Apps"],"operationId":"v2_apps_patch","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"displayName":{"description":"Human-readable app name shown in the admin panel, sign-up\nscreens, and chat headers. 3-100 chars.\n","type":"string"},"domainName":{"description":"Subdomain used for the hosted app at\n`<domainName>.<rootDomain>`. Alphanumeric only (no\ndashes); must be unique across the platform.\n","type":"string"},"appTagline":{"description":"Short marketing line shown beneath the display name on\nthe sign-up screen. Free-form.\n","type":"string"},"appDescription":{"description":"Alias of `appTagline` accepted on update endpoints for\nclarity. If both are sent, they end up in the same field.\n","type":"string"},"logoImage":{"description":"Main app logo. POST/PUT accept either a file upload\n(multipart) or a URL (JSON). 1:1 ratio recommended,\ndisplayed at 120x120 px in the admin panel.\n","type":"string"},"sublogoImage":{"description":"Optional secondary logo / favicon. Same input shapes as\n`logoImage`.\n","type":"string"},"primaryColor":{"description":"Brand colour as a 6-digit hex string (e.g. `#0052CD`).\nUsed as the accent throughout the chat UI.\n","type":"string"},"bundleId":{"description":"iOS/Android bundle identifier in reverse-DNS form (e.g.\n`com.acme.chat`). Required for native push notifications.\n","type":"string"},"coinSymbol":{"description":"Short ticker for the app's coin (e.g. `ETO`). Used in\ntoken balances and transaction summaries.\n","type":"string"},"coinName":{"description":"Display name for the app's coin (e.g. `Ethora Coin`).\n","type":"string"},"defaultProfileIsPublic":{"type":"boolean","description":"Default value of `profileIsPublic` for newly-created\nusers in this app. If true, anyone in the app can view\na new user's profile by default; if false, only the\nuser themself + admins.\n"},"defaultAssetsArePublic":{"type":"boolean","description":"Default value of `assetsArePublic` for newly-created\nusers in this app. If true, a new user's owned tokens\n/ assets are visible on their public profile by\ndefault; if false, hidden.\n"},"allowUsersToBecomeTenants":{"type":"boolean","description":"If true, end-users of this app can become tenants\nthemselves (create their own apps, take their account\nindependent of this app). Used for the base app where\nany signup can spin up their own apps. Default false\nfor child apps.\n"},"defaultAccessProfileOpen":{"deprecated":true,"type":"boolean","description":"**Deprecated** - kept as a backward-compatible alias\nfor `defaultProfileIsPublic`. New integrations should\nuse `defaultProfileIsPublic`.\n"},"defaultAccessAssetsOpen":{"deprecated":true,"type":"boolean","description":"**Deprecated** - kept as a backward-compatible alias\nfor `defaultAssetsArePublic`. New integrations should\nuse `defaultAssetsArePublic`.\n"},"usersCanFree":{"deprecated":true,"type":"boolean","description":"**Deprecated** - kept as a backward-compatible alias\nfor `allowUsersToBecomeTenants`. New integrations\nshould use `allowUsersToBecomeTenants`.\n"},"isAllowedNewAppCreate":{"description":"If true, end-users of this app see an \"Create your own\napp\" affordance after login. Typically only the base app\nhas this on.\n","type":"boolean"},"platformPushEnabled":{"description":"Platform-key pushes (the base app's FCM/APNs keys serve\nevery app of the install) are on by default; false\ncreates the app with them switched off, the same switch\nas PUT /v1/push/platform/{appId}. If the push service\ncannot be reached, the app is still created and the\nswitch can be set afterwards.\n","type":"boolean"},"afterLoginPage":{"description":"Page the user lands on after login. One of `chats`,\n`profile`.\n","type":"string","enum":["profile","chats","admin"]},"allowUsersToCreateRooms":{"description":"If true, end-users can create their own chat rooms. If\nfalse, only admins can create rooms via the API.\n","type":"boolean"},"e2eeEnabled":{"description":"If true, this app's users can open end-to-end encrypted\n(OMEMO 2) 1:1 chats: `POST /v1/chats/private` accepts\n`e2ee: true`, and `GET /apps/get-config` reports the flag\nso the client shows the affordance. If false (the default),\nthat request answers `403 E2EE_DISABLED` and only plaintext\nrooms can be created. The install-wide `features.e2ee`\ndeploy flag is the outer gate - a chat is encryptable only\nwhen both are on. Turning this off stops NEW encrypted\nrooms being created; rooms already encrypted stay so.\n","type":"boolean"},"userRegistrationDisabled":{"description":"If true, self-service registration is closed for this app:\n`POST /v1/users`, `POST /v1|v2/users/sign-up-with-email` and\n`POST /v1/users/sign-up-resend-email` answer\n`403 REGISTRATION_DISABLED`. Owner/B2B provisioning\n(`/users/create-with-app-id/{id}`, `/users/batch`,\n`/v2/apps/{appId}/users/batch`) and all login, refresh and\npassword-reset flows are unaffected. Negative on purpose:\napps that predate the field have no such key and stay\nregistration-allowed with no migration.\n","type":"boolean","default":false},"requireMfaForAdmins":{"description":"When true, users who hold an admin ACL on this app (or are\nplatform super admins) and have not enrolled in MFA get\n`mfaEnrolmentRequired: true` in the login response, and the\nadmin panel steers them to Account > Security. Login is not\nblocked; end users and API clients are unaffected.\n","type":"boolean","default":false},"broadcastSender":{"type":"object","description":"Per-app default identity stamped on broadcast announcements\nwhen the broadcast caller doesn't supply `sender` in the\nPOST /v2/chats/broadcast (or /v2/apps/{appId}/chats/broadcast)\nbody. Without this, chat-component renders broadcasts as\n\"Deleted User\" because the system chat account JID is not\nin usersSet. Pass an empty string for either field to clear it.","properties":{"name":{"type":"string","maxLength":120,"example":"Acme Health"},"photoUrl":{"type":"string","format":"uri","maxLength":2048,"example":"https://cdn.example.com/logo.png"}}}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"app not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]},"delete":{"summary":"Archive (soft-delete) or purge an app (v2)","description":"**Auth**: Tenant Actor\n\nDefault behavior is **soft archive**: the app's `status` flips to `archived`,\nlogin and listing endpoints hide it, and all child entities (users, chats,\nmessages, files) are retained. The archive is reversible via\n`POST /v2/apps/{appId}/restore`.\n\nPass `?mode=hard` for a **cascade purge** (irreversible). The app is\nimmediately flipped to `status: 'deleting'` and a backend-jobs worker\ndrains every related collection (users + XMPP accounts, chats + MUC\nrooms + MAM, files + MinIO objects, sources, AppAcl, RefreshToken,\nSessionsLog, BotInstance, AppStat, Owner2App, App doc).\n\nThe hard-delete response is `202 Accepted` with a `jobId` and `statusUrl`\nso the admin panel can poll progress via\n`GET /v2/apps/purge-jobs/{jobId}`. Soft archive returns `200` immediately\nsince it has no async work.\n\nOptional `?reason=...` is stored alongside the archive metadata for audit\npurposes (visible to the tenant owner via the list endpoint with\n`?status=archived`).\n\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Apps"],"operationId":"v2_apps_delete","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"query","name":"mode","required":false,"schema":{"type":"string","enum":["soft","hard"],"default":"soft"},"description":"`soft` (default): reversible archive, returns 200.\n`hard`: cascade purge, returns 202 + jobId.\n"},{"in":"query","name":"reason","required":false,"schema":{"type":"string","maxLength":500},"description":"Optional human-readable explanation stored with the archive."}],"responses":{"200":{"description":"archived ok (mode=soft)"},"202":{"description":"purge enqueued (mode=hard); poll the returned statusUrl"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"app not found"},"409":{"description":"app already in an unrecoverable state (deleted)"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v2/apps/{appId}/restore":{"post":{"summary":"Restore a previously archived app (v2)","description":"Flips an `archived` app back to `active`. Child entities (users, chats,\nmessages, files) become accessible again because they were never touched\nby the archive step.\n\nIdempotent: restoring an already-active app returns 200 with\n`alreadyActive: true`.\n\nCannot restore an app whose cascade purge has started (`status: 'deleting'`)\nor completed (`status: 'deleted'`) - returns 409.\n\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Apps"],"operationId":"v2_apps_restore","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"restored ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"app not found"},"409":{"description":"app is in a non-restorable state"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/apps/{appId}/export":{"get":{"summary":"Export an App and its child entities as a portable bundle (v2)","description":"Returns a versioned bundle (`schemaVersion: ethora.app.bundle.v1`)\ncontaining the App doc (secrets stripped) and selected child entities.\n\nUse cases:\n- **Backup**: customers can snapshot an app and its assets as JSON.\n- **Migration**: move an app between environments by exporting from\n  the source and re-importing at the destination.\n\nAuthorization: only the app's owner or a super-admin can export.\n\nFormat: `json` returns the bundle directly (Content-Type:\napplication/json with download disposition). `zip` returns a zip\narchive containing `bundle.json`.\n\nDefault include set is `chats,sources,botInstances` (structural\ndata without PII). Users are opt-in via `include=users` because\nthey carry email addresses and other personal data.\n","tags":["Apps"],"operationId":"v2_apps_export","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"query","name":"format","description":"Bundle format: `json` (default) or `zip` (same content, zipped\nfor transport).\n","schema":{"type":"string","enum":["json","zip"],"default":"json"}},{"in":"query","name":"include","schema":{"type":"string"},"description":"CSV of categories: `chats,users,sources,botInstances`. Pass\n`all` for everything. Default excludes `users` for PII safety.\n"}],"responses":{"200":{"description":"bundle"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"app not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/apps/import":{"post":{"summary":"Import a previously-exported App bundle (v2)","description":"**Auth**: Tenant Actor\n\nCreates a NEW App owned by the calling tenant from the bundle.\nThe source `_id`, `domainName`, secrets, and tokens are all\nregenerated. Chat names are remapped from `${oldAppId}_<uuid>` to\n`${newAppId}_<uuid>` so the XMPP per-app prefix invariant holds.\n\nWhen the export was created with `include=users`, the bundle also\nround-trips end users: bcrypt password hashes and `xmppPassword`\nare preserved, `xmppUsername` is remapped to\n`<newAppId>_<newUserId>`, and `User2Chats` memberships are\nrewritten with the fresh IDs. Imported users can log in with\ntheir original credentials. Bundles containing user hashes are\nsensitive - the import response calls this out in `summary.notes`.\n\nStill skipped (called out in the response `notes`):\n- botInstances (per-app embodiments reference user IDs that don't\n  exist in the new environment; recreate via\n  `/v2/agents/:idOrAddress/invite-to-chat`).\n\nBody shapes:\n- `application/json`: body IS the bundle.\n- `multipart/form-data`: field `bundle` is a `.zip` containing\n  `bundle.json`.\n\n`?domainNameOverride=` lets the caller pick a new domainName up\nfront; the importer auto-suffixes on collision otherwise.\n","tags":["Apps"],"operationId":"v2_apps_import","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}},"multipart/form-data":{"schema":{"type":"object","properties":{"bundle":{"description":"The exported bundle to import. Either a JSON document\nmatching `ethora.app.bundle.v1` or a `.zip` of that\ndocument.\n","type":"string","format":"binary"}}}}}},"responses":{"201":{"description":"imported"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"bundle missing, malformed, or wrong schemaVersion"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v2/agents/{id}/reply":{"post":{"summary":"One-shot synchronous ask - text in, the agent's reply out (b2b)","description":"Sends the text straight to the agent's LLM persona (plus its RAG\nknowledge base when enabled) and returns the reply in the same HTTP\nresponse - no chat room or XMPP involved. Built for server-to-server\nautomations (e.g. n8n flows). Requires a b2b token: the call bypasses\nroom membership entirely. Responses can take 10-30s for long\ngenerations - set client timeouts accordingly.\n","tags":["Agents"],"parameters":[{"in":"path","name":"id","description":"Agent id","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":8000}}}}}},"responses":{"200":{"description":"ok ({ ok, agentId, reply, model, ragDocsUsed, totalTokens })"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"agent not found"},"422":{"description":"validation error"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"description":"AI service not configured"}},"operationId":"post_v2_agents_id_reply"}},"/v2/agents/{idOrAddress}/export":{"get":{"summary":"Export an Agent's configuration as a portable bundle (v2)","description":"Returns `schemaVersion: ethora.agent.bundle.v1` containing the\nagent doc with identifiers stripped. The bundle holds the agent's\n*configuration* (prompt, RAG tags, model choice, heartbeat, etc.)\nbut NOT the RAG corpus - sources are owned by the App and the\ndestination should reindex from the original URLs.\n\nAuthorization: owner of the agent, super-admin, or anyone if the\nagent's `visibility` is `public`.\n","tags":["Agents"],"operationId":"v2_agents_export","parameters":[{"in":"path","name":"idOrAddress","required":true,"schema":{"type":"string"}},{"in":"query","name":"format","schema":{"type":"string","enum":["json","zip"],"default":"json"}}],"responses":{"200":{"description":"bundle"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/agents/import":{"post":{"summary":"Import an Agent bundle (v2)","description":"Creates a new Agent under the calling user with a fresh EOA-style\naddress. Visibility is forced to `private` regardless of the\nbundle - the importer must deliberately re-publish.\n\nBody shapes:\n- `application/json`: body IS the bundle.\n- `multipart/form-data`: field `bundle` is a `.zip` containing\n  `bundle.json`.\n\nPass `?ownerAppId=` to attribute the new agent to a specific home\napp for analytics; otherwise falls back to the caller's appId.\n","tags":["Agents"],"operationId":"v2_agents_import","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}},"multipart/form-data":{"schema":{"type":"object","properties":{"bundle":{"type":"string","format":"binary"}}}}}},"responses":{"201":{"description":"imported"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"bundle missing, malformed, or wrong schemaVersion"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/agents":{"get":{"summary":"List AI Agents (v2)","description":"Lists Agents visible to the caller, most recently updated first.\nSupports `User JWT` and `B2B Server JWT`. With a B2B Server JWT the\ncaller (and so the owner of agents it creates) is the token's tenant App.\n\n`visibility` selects the set:\n- omitted (default): the caller's own agents plus every `public` agent.\n  `unlisted` agents of other owners are not listed.\n- `public`: public agents only.\n- `mine`: the caller's own agents, any visibility.\n- `all` / `private`: platform readers (`isSuperAdmin.read`) get every\n  agent, or every private agent across all owners. For anyone else\n  these values fall back to the default set.\n\nEach item is an Agent object (see `GET /v2/agents/{idOrAddress}`) with\n`originAppName`, `botInstancesCount` and the live `totalSiteSourceSize`\nfilled in.\n","tags":["Agents"],"operationId":"v2_agents_list","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"query","name":"visibility","required":false,"schema":{"type":"string","enum":["public","mine","all","private"]}},{"in":"query","name":"appId","description":"Only agents whose home App (`ownerAppId`) is this App.","required":false,"schema":{"type":"string"}},{"in":"query","name":"category","description":"Only agents tagged with this category. Spelling is normalised\n(\"Customer support\" and \"customer_support\" both mean `customer-support`).\n","required":false,"schema":{"type":"string","enum":["customer-support","persona","entertainment","worker"]}},{"in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"in":"query","name":"offset","required":false,"schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"```\n{\n  ok: true,\n  total: number,              // matching agents, ignoring limit/offset\n  items: [Agent],\n  results: [Agent],           // same as items\n  pagination: { total: number }\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Invalid query parameter, or a category that is not on the list.\n```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"summary":"Create an AI Agent (v2)","description":"Creates an Agent owned by the caller, with a fresh EOA-style `address`.\nSupports `User JWT` and `B2B Server JWT` (the owner is then the\ntoken's tenant App).\n\nAn Agent is account-level: it is embodied in an App as a BotInstance\nthe first time it is invited into one of that App's chats\n(`POST /v2/agents/{idOrAddress}/invite-to-chat`).\n\nDefaults for omitted fields: `displayName` \"AI Agent\", `prompt`\n\"You are a helpful assistant.\", `responseMode` `smart`,\n`responseProbability` 1, `cooldownSec` 0, `isRAG` true,\n`visibility` `private`, `ownerAppId` the caller's App.\n\nOlder field names are still accepted and mapped: `name` and\n`botDisplayName` to `displayName`, `summary` to `bio`, `botAvatarUrl`\nto `avatarUrl`, `isPublished: true` to `visibility: public`. `slug`\nand `meta` are accepted and ignored. Any other unknown field is a 422.\n\nRequires the `ai` license feature and the account's agents capability\n(a platform admin can turn agent creation off per account).\n","tags":["Agents"],"operationId":"v2_agents_create","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"displayName":{"type":"string","description":"Name shown in chats (also the bot user's first/last name)."},"avatarUrl":{"type":"string"},"bio":{"type":"string"},"prompt":{"type":"string","description":"System prompt (the agent's instructions)."},"llmProvider":{"type":"string","description":"Empty means the platform default."},"llmModel":{"type":"string","description":"Empty means the platform default."},"embeddingModel":{"type":"string"},"contextSize":{"type":"integer","minimum":0},"responseMode":{"type":"string","enum":["always","mentioned","smart","probability"],"description":"When the agent replies in a room. `always` answers every\nhuman message, `mentioned` only when addressed,\n`smart` decides per message, `probability` replies with\n`responseProbability`.\n"},"responseProbability":{"type":"number","minimum":0,"maximum":1},"cooldownSec":{"type":"integer","minimum":0,"description":"Minimum seconds between two replies in one room."},"greetingMessage":{"type":"string"},"isRAG":{"type":"boolean","description":"Answer with the agent's knowledge (Web Index and Docs Index)."},"ragTags":{"type":"array","maxItems":50,"items":{"type":"string"},"description":"Restrict retrieval to sources carrying these tags."},"categories":{"type":"array","maxItems":16,"items":{"type":"string","enum":["customer-support","persona","entertainment","worker"]},"description":"Directory categories. Values outside the list are a 422;\nspelling is normalised and duplicates dropped.\n"},"soulMd":{"type":"string","description":"The agent's long-lived notes, shown as \"Memory\" in the admin\nUI. Later edits go through `POST /v2/agents/{idOrAddress}/soul`.\n"},"heartbeat":{"type":"object","properties":{"enabled":{"type":"boolean"},"schedule":{"type":"string"},"prompt":{"type":"string"}}},"flowsYaml":{"type":"string","maxLength":65536,"description":"Flows script (scripted conversations) in YAML. Compiled on\nsave; a script that does not compile is a 400 and nothing is\nsaved. Check one first with `POST /v2/agents/flows/validate`.\n"},"visibility":{"type":"string","enum":["private","unlisted","public"]},"ownerAppId":{"type":"string","description":"Home App of the agent. Must be an App the caller manages,\notherwise 403 `OWNER_APP_FORBIDDEN`.\n"}}}}}},"responses":{"201":{"description":"```\n{ ok: true, agent: Agent }\n```"},"400":{"description":"The flows script does not compile.\n```\n{ error: 'Invalid flows script', code: 'FLOWS_INVALID', details: [{ path, message, line? }] }\n```"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`OWNER_APP_FORBIDDEN` (ownerAppId is not an App the caller manages),\n`AGENTS_DISABLED` (agent creation is turned off for this account), or\n`LICENSE_FEATURE_NOT_INCLUDED` (the edition has no `ai` feature)."},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Validation error, including an unknown category.\n```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/agents/flows/validate":{"post":{"summary":"Check a flows script without saving it (v2)","description":"Runs the flows compiler on a YAML script and reports the result. Nothing\nis stored. Supports `User JWT` and `B2B Server JWT`.\n\nA script that does not compile is still a 200, with `ok: false` and\nthe errors (each with a 1-based `line` when it can be located).\n","tags":["Agents"],"operationId":"v2_agents_flows_validate","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["flowsYaml"],"properties":{"flowsYaml":{"type":"string","maxLength":65536}}}}}},"responses":{"200":{"description":"```\n{ ok: true, flowKeys: string[] }\n// or\n{ ok: false, errors: [{ path: string, message: string, line?: number }] }\n```"},"400":{"description":"`flowsYaml` missing or too long.\n```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/agents/{idOrAddress}":{"get":{"summary":"Get an AI Agent (v2)","description":"Returns one Agent. Supports `User JWT` and `B2B Server JWT`.\n\nA `private` agent is readable by its owner and platform readers\n(`isSuperAdmin.read`) only. `unlisted` and `public` agents are readable\nby any authenticated caller, including platform-owned agents such as\nthe Support Agent template (`ownerId: 'system'`).\n","tags":["Agents"],"operationId":"v2_agents_get","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n  ok: true,\n  agent: {\n    id: string,\n    address: string,              // EOA-style, 0x + 40 hex\n    ownerId: string,              // user id, tenant App id (B2B), or 'system'\n    ownerAppId: string|null,      // home App\n    originAppId: string|null,     // alias of ownerAppId\n    originAppName: string|null,   // list/get only, null on write responses\n    botInstancesCount: number|null, // list/get only, null on write responses\n    displayName: string,\n    avatarUrl: string,\n    bio: string,\n    prompt: string,\n    llmProvider: string,\n    llmModel: string,\n    embeddingModel: string,\n    contextSize: number,\n    responseMode: 'always'|'mentioned'|'smart'|'probability',\n    responseProbability: number,\n    cooldownSec: number,\n    greetingMessage: string,\n    isRAG: boolean,\n    ragTags: string[],\n    categories: string[],         // customer-support | persona | entertainment | worker\n    soulMd: string,               // \"Memory\" in the admin UI\n    soulMdUpdatedAt: ISODate|null,\n    soulMdUpdatedBy: 'operator'|'agent'|null,\n    heartbeat: { enabled: boolean, schedule: string, prompt: string },\n    flowsYaml: string,\n    flows: object[],              // compiled flows\n    visibility: 'private'|'unlisted'|'public',\n    totalSiteSourceSize: number,  // indexed knowledge, bytes\n    createdAt: ISODate,\n    updatedAt: ISODate\n  }\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Private agent of another owner.\n```\n{ error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"description":"```\n{ error: 'Agent not found', code: 'AGENT_NOT_FOUND' }\n```"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"summary":"Update an AI Agent (v2)","description":"Updates the fields present in the body; omitted fields keep their\nvalue. Owner only: anyone else, platform admins included, gets 403.\nPlatform-owned agents (`ownerId: 'system'`, such as the Support Agent\ntemplate) are read-only to tenants, who clone them instead\n(`POST /v2/agents/{idOrAddress}/clone`). Supports `User JWT` and\n`B2B Server JWT`.\n\nTakes the same body as `POST /v2/agents`, all fields optional.\n`soulMd` (Memory) and `visibility` are applied as by\n`POST /v2/agents/{idOrAddress}/soul` and `.../visibility`; a new\n`ownerAppId` must be an App the caller manages (else 403\n`OWNER_APP_FORBIDDEN`).\n\nRunning bots of this agent pick the change up straight away, and a new\n`displayName` or `avatarUrl` is copied onto the bot users so chats\nshow it.\n","tags":["Agents"],"operationId":"v2_agents_update","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Same fields as `POST /v2/agents`, all optional.","additionalProperties":true}}}},"responses":{"200":{"description":"```\n{ ok: true, agent: Agent }\n```"},"400":{"description":"The flows script does not compile.\n```\n{ error: 'Invalid flows script', code: 'FLOWS_INVALID', details: [{ path, message, line? }] }\n```"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The caller does not own the agent.\n```\n{ error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"description":"```\n{ error: 'Agent not found', code: 'AGENT_NOT_FOUND' }\n```"},"422":{"description":"Validation error, including an unknown field or category.\n```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"summary":"Delete an AI Agent (v2)","description":"Deletes the agent. Owner only. Supports `User JWT` and `B2B Server JWT`.\n\nIts BotInstances are switched off (`status: off`) rather than removed,\nso their bot users stay in place. The agent's own knowledge (Web Index\nand Docs Index rows carrying its id, and their embeddings) is deleted\nwith it; `knowledgeDeleted` reports the counts when that step ran.\n","tags":["Agents"],"operationId":"v2_agents_delete","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{ ok: true, knowledgeDeleted?: { sites: number|null, docs: number|null, chunks: number|null } }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The caller does not own the agent.\n```\n{ error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"description":"```\n{ error: 'Agent not found', code: 'AGENT_NOT_FOUND' }\n```"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/agents/{idOrAddress}/clone":{"post":{"summary":"Clone an AI Agent (v2)","description":"Creates a copy owned by the caller, with a fresh address. The copy\ntakes the source's configuration (prompt, model, response settings,\ncategories, Memory, heartbeat, flows) and always starts `private`.\nThis is how tenants customise a platform-owned agent such as the\nSupport Agent template.\n\nAny agent that is not `private` can be cloned; a private one only by\nits owner. Supports `User JWT` and `B2B Server JWT`. Requires the\naccount's agents capability.\n\nWith `includeKnowledge: true` the source's knowledge (Web Index and\nDocs Index rows carrying the source's id, and their embeddings) is\ncopied into the clone's home App under the clone's id. The clone is\ncreated either way; `knowledge` says what was copied.\n","tags":["Agents"],"operationId":"v2_agents_clone","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"displayName":{"type":"string","description":"Name of the copy. Default is the source's name plus \" (copy)\"."},"ownerAppId":{"type":"string","description":"Home App of the copy. Must be an App the caller manages.\nDefault is the caller's App.\n"},"includeKnowledge":{"type":"boolean","default":false,"description":"Also copy the source's web/doc knowledge and embeddings."}}}}}},"responses":{"201":{"description":"```\n{\n  ok: true,\n  agent: Agent,\n  // only when includeKnowledge was true:\n  knowledge?: { copied: true, sites: number, docs: number, chunks: number|null }\n            | { copied: false, reason: string }\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`FORBIDDEN` (private agent of another owner), `OWNER_APP_FORBIDDEN`\n(ownerAppId is not an App the caller manages), or `AGENTS_DISABLED`."},"404":{"description":"```\n{ error: 'Agent not found', code: 'AGENT_NOT_FOUND' }\n```"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/agents/{idOrAddress}/visibility":{"post":{"summary":"Set an AI Agent's visibility (v2)","description":"`private`: only the owner sees and uses it. `unlisted`: anyone with\nthe id or address can read, clone, try and invite it, but it is not\nlisted. `public`: also listed for every account.\n\nOwner, or a platform admin with `isSuperAdmin.write` (for moderation).\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Agents"],"operationId":"v2_agents_visibility_set","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["visibility"],"properties":{"visibility":{"type":"string","enum":["private","unlisted","public"]}}}}}},"responses":{"200":{"description":"```\n{ ok: true, agent: Agent }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n{ error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"description":"```\n{ error: 'Agent not found', code: 'AGENT_NOT_FOUND' }\n```"},"422":{"description":"```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/agents/{idOrAddress}/soul":{"post":{"summary":"Replace or append to an AI Agent's Memory (v2)","description":"Edits `soulMd`, the agent's long-lived notes, shown as \"Memory\" in the\nadmin UI. `soulMd` replaces the whole text; `append` adds a line to the\nend. Send one of them (if both are sent, `soulMd` wins). Running bots\nof this agent get the new text straight away.\n\nOwner only. Supports `User JWT` and `B2B Server JWT`.\n","tags":["Agents"],"operationId":"v2_agents_soul_update","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"soulMd":{"type":"string","description":"New Memory text (replaces the current one; \"\" clears it)."},"append":{"type":"string","description":"Text added at the end, on a new line."}}}}}},"responses":{"200":{"description":"```\n{ ok: true, agent: Agent }   // soulMdUpdatedBy: 'operator'\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n{ error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"description":"```\n{ error: 'Agent not found', code: 'AGENT_NOT_FOUND' }\n```"},"422":{"description":"Neither `soulMd` nor `append` given.\n```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/agents/{idOrAddress}/knowledge":{"get":{"summary":"Knowledge health of an AI Agent (v2)","description":"Compares the agent's stored knowledge (Web Index pages and Docs Index\ndocuments carrying its id) with the searchable chunks in the AI\nservice, and reports what is stored but not searchable. Fix gaps with\n`POST /v2/agents/{idOrAddress}/knowledge/rebuild`.\n\nWhen the AI service cannot be reached the counts of stored rows are\nstill returned, with `indexAvailable: false` and `chunks: null`.\n\nOwner only. Supports `User JWT` and `B2B Server JWT`.\n","tags":["Agents"],"operationId":"v2_agents_knowledge_health","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n  ok: true,\n  pages: number,              // Web Index rows\n  docs: number,               // Docs Index rows\n  chunks: number|null,        // searchable chunks, null when the AI service is unreachable\n  missing: {\n    pages: number,            // stored but not searchable\n    docs: number,\n    examples: string[]        // up to 5 URLs / file names\n  },\n  lastIndexedAt: ISODate|null,\n  indexAvailable: boolean,\n  rebuilding: boolean         // a rebuild is running\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n{ error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"description":"```\n{ error: 'Agent not found', code: 'AGENT_NOT_FOUND' }\n```"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/agents/{idOrAddress}/knowledge/rebuild":{"post":{"summary":"Rebuild an AI Agent's knowledge index (v2)","description":"Re-embeds the agent's stored pages and documents from their saved\ntext, without crawling or uploading anything again. Runs in the\nbackground: the response says how much was queued, and\n`GET /v2/agents/{idOrAddress}/knowledge` shows `rebuilding: true` until\nit finishes.\n\n`onlyMissing: true` (the default) re-embeds only what has no chunks.\n`onlyMissing: false` drops all of the agent's chunks first and embeds\neverything again.\n\nOwner only. Supports `User JWT` and `B2B Server JWT`. Requires the\n`ai` license feature.\n","tags":["Agents"],"operationId":"v2_agents_knowledge_rebuild","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"onlyMissing":{"type":"boolean","default":true}}}}}},"responses":{"202":{"description":"Rebuild started.\n```\n{ ok: true, pages: number, docs: number }   // queued for embedding\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`FORBIDDEN` (not the owner) or `LICENSE_FEATURE_NOT_INCLUDED`."},"404":{"description":"```\n{ error: 'Agent not found', code: 'AGENT_NOT_FOUND' }\n```"},"409":{"description":"```\n{ error: 'The index is already being rebuilt', code: 'REBUILD_IN_PROGRESS' }\n```"},"422":{"description":"```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"description":"```\n{ error: 'Could not reach the AI service', code: 'AI_SERVICE_ERROR' }\n```"},"503":{"description":"```\n{ error: 'AI service is not configured', code: 'AI_SERVICE_UNAVAILABLE' }\n```"}}}},"/v2/agents/{idOrAddress}/try":{"post":{"summary":"Test conversation with an AI Agent, without a chat room (v2)","description":"One turn of a test chat: the agent answers `text` with its\ninstructions, Memory and knowledge, given the conversation so far in\n`history`. No room, no XMPP, nothing is stored; the client keeps the\nhistory and sends it back on the next turn.\n\nOpen to the owner, and to anyone for an `unlisted` or `public` agent\n(so it can be tried before cloning). A private agent of another owner\nreads as 404. Supports `User JWT` and `B2B Server JWT`. Requires the\n`ai` license feature. Replies can take tens of seconds.\n","tags":["Agents"],"operationId":"v2_agents_try","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","minLength":1,"maxLength":4000},"history":{"type":"array","maxItems":20,"default":[],"items":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["user","assistant"]},"content":{"type":"string","maxLength":8000}}}}}}}}},"responses":{"200":{"description":"```\n{ ok: true, reply: string, sources: object[], ragDocsUsed: number, model: string|null }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The edition has no `ai` feature (`LICENSE_FEATURE_NOT_INCLUDED`)."},"404":{"description":"```\n{ error: 'Agent not found', code: 'AGENT_NOT_FOUND' }\n```"},"422":{"description":"```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"description":"```\n{ error: 'The agent could not answer', code: 'AI_SERVICE_ERROR' }\n```"},"503":{"description":"```\n{ error: 'AI service is not configured', code: 'AI_SERVICE_UNAVAILABLE' }\n```"}}}},"/v2/agents/{idOrAddress}/bot-instances":{"get":{"summary":"List the BotInstances of an AI Agent (v2)","description":"A BotInstance is the agent's embodiment in one App (its bot user and\nXMPP account there). Supports `User JWT` and `B2B Server JWT`.\n\nA private agent's list is readable by its owner only. For an agent\nthe caller does not own (a public agent, the Support Agent template\nabove all, has bots in many accounts' Apps) only bots in Apps the\ncaller created are returned; platform readers (`isSuperAdmin.read`)\nsee all of them.\n","tags":["Agents"],"operationId":"v2_agents_bot_instances_list","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n  ok: true,\n  items: [\n    {\n      id: string,\n      agentId: string,\n      appId: string,\n      appName: string,            // the App's displayName, '' when unknown\n      isAppWidgetBot: boolean,    // the App's website widget answers with this bot\n      userId: string,             // the bot user\n      xmppUsername: string,\n      status: 'on'|'off',\n      joinedRooms: string[],      // room JIDs\n      joinedRoomsDetails: [{ jid: string, name: string, title: string }],\n      lastActiveAt: ISODate|null,\n      createdAt: ISODate,\n      updatedAt: ISODate\n    }\n  ],\n  results: [...same as items...],\n  pagination: {}\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Private agent of another owner.\n```\n{ error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"description":"```\n{ error: 'Agent not found', code: 'AGENT_NOT_FOUND' }\n```"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/agents/{idOrAddress}/bot-instances/{botInstanceId}/diag":{"get":{"summary":"Diagnostics for one BotInstance of an AI Agent (v2)","description":"Returns the stored BotInstance plus the AI service's live view of it\n(XMPP connected or not, joined rooms, last error, recent\nconversations). When the AI service cannot be reached the call still\nsucceeds, with `aiService: null` and the reason in `aiServiceError`.\n\nFor a private agent, owner only. For someone else's public agent,\nonly bots in Apps the caller manages (403 otherwise). Supports\n`User JWT` and `B2B Server JWT`.\n","tags":["Agents"],"operationId":"v2_agents_bot_instance_diag","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}},{"in":"path","name":"botInstanceId","description":"BotInstance id (from the agent's bot-instances list).","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n  ok: true,\n  botInstance: {\n    id: string, agentId: string, appId: string, xmppUsername: string,\n    status: 'on'|'off', joinedRooms: string[], lastActiveAt: ISODate|null\n  },\n  aiService: object|null,     // live state reported by the AI service\n  aiServiceError: string|null\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n{ error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"description":"`AGENT_NOT_FOUND`, or `BOT_INSTANCE_NOT_FOUND` (no such BotInstance\nfor this agent)."},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/agents/{idOrAddress}/bot-instances/{botInstanceId}/test-message":{"post":{"summary":"Make a BotInstance post a test message (v2)","description":"The bot posts a system message into `roomJid`, or into every room it is\ncurrently in when `roomJid` is omitted. Confirms that the bot is\nconnected and can deliver messages. The AI service's answer is passed\nthrough, status code included.\n\nOwner only. Supports `User JWT` and `B2B Server JWT`.\n","tags":["Agents"],"operationId":"v2_agents_bot_instance_test_message","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}},{"in":"path","name":"botInstanceId","description":"BotInstance id (from the agent's bot-instances list).","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":2000,"description":"Message text. Default is \"(test message from <bot name>)\"."},"roomJid":{"type":"string","description":"Only this room. The bot must already be in it."}}}}}},"responses":{"200":{"description":"```\n{ ok: true, sent: number, total: number, results: [{ room: string, ok: boolean, error?: string }] }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n{ error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"description":"`AGENT_NOT_FOUND`, `BOT_INSTANCE_NOT_FOUND`, or `BOT_NOT_SPAWNED`\n(the AI service has no running bot for it)."},"409":{"description":"From the AI service: `BOT_OFFLINE` (XMPP not connected),\n`BOT_NO_ROOMS`, or `BOT_NOT_IN_ROOM` (with the bot's `rooms`)."},"422":{"description":"```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"description":"AI service not configured on this install.\n```\n{ error: 'AI_SERVICE_URL/SECRET not configured', code: 'AI_SERVICE_DOWN' }\n```"},"502":{"description":"The AI service could not be reached.\n```\n{ error: string, code: 'AI_SERVICE_DOWN' }\n```"}}}},"/v2/agents/{idOrAddress}/bot-instances/{botInstanceId}/leave-chat":{"post":{"summary":"Remove a BotInstance from one chat (v2)","description":"Takes the bot out of one room without stopping it or deleting the\nBotInstance: the AI service leaves the room, the bot's room\naffiliation is removed so it does not rejoin on reconnect, and it is\ndropped from the room's member list.\n\nThe AI service step is best-effort: when it fails the rest still runs\nand `aiServiceOk: false` comes back with the reason.\n\nOwner only. Supports `User JWT` and `B2B Server JWT`.\n","tags":["Agents"],"operationId":"v2_agents_bot_instance_leave_chat","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}},{"in":"path","name":"botInstanceId","description":"BotInstance id (from the agent's bot-instances list).","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["chatJid"],"properties":{"chatJid":{"type":"string","description":"Full room JID, `<appId>_<chatId>@conference.<xmpp host>`."}}}}}},"responses":{"200":{"description":"```\n{\n  ok: true,\n  aiServiceOk: boolean,\n  aiServiceError: string|null,\n  botInstance: { id: string, xmppUsername: string, joinedRooms: string[], status: 'on'|'off' }\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n{ error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"description":"`AGENT_NOT_FOUND`, or `BOT_INSTANCE_NOT_FOUND` (no such BotInstance\nfor this agent)."},"422":{"description":"```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/agents/{idOrAddress}/invite-to-chat":{"post":{"summary":"Add an AI Agent to a chat (v2)","description":"Adds the agent to a room. The first invite into an App creates the\nagent's BotInstance there (bot user, XMPP account, a running bot);\nlater invites into that App reuse it. The bot is made a room member,\nlisted among the room's members, and told to join.\n\nThe agent must be the caller's own, or `unlisted` / `public`. The\nroom must belong to the target App (else 422 `CHAT_APP_MISMATCH`), and\nthe caller must manage that App (its creator, an admin with\n`appSettings.update`, a B2B Server JWT for it, `isSuperAdmin.write`)\nor be a member of the room (else 403). Supports `User JWT` and\n`B2B Server JWT`.\n\nThe target App is `appId` from the body, else the caller's App. Name\nthe room with `chatJid` (full room JID) or `chatId` (the chat's id).\n","tags":["Agents"],"operationId":"v2_agents_invite_to_chat","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"One of `chatJid` or `chatId` is required.","properties":{"appId":{"type":"string","description":"App the room belongs to. Default is the caller's App."},"chatJid":{"type":"string","description":"Full room JID, `<appId>_<chatId>@conference.<xmpp host>`."},"chatId":{"type":"string","description":"The chat's id; used when `chatJid` is not given."}}}}}},"responses":{"200":{"description":"```\n{\n  ok: true,\n  created: boolean,           // a new BotInstance was created for this App\n  agent: Agent,\n  botInstance: {\n    id: string, agentId: string, appId: string, userId: string,\n    xmppUsername: string, status: 'on'|'off', joinedRooms: string[]\n  },\n  chatJid: string\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n{ error: 'Agent is private', code: 'AGENT_PRIVATE' }\n```"},"404":{"description":"`AGENT_NOT_FOUND`, `APP_NOT_FOUND`, or `CHAT_NOT_FOUND` (no chat\nwith that `chatId`)."},"422":{"description":"Neither `chatJid` nor `chatId`, or no App to invite into.\n```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"description":"```\n{ error: 'XMPP affiliate/invite failed', code: 'XMPP_UNAVAILABLE', details: { reason: string } }\n```"}}}},"/v2/agents/{idOrAddress}/activate":{"post":{"summary":"Make an AI Agent the App's website widget responder (v2, app-token)","description":"Invites the agent into the App's AI Widget chat (as\n`POST /v2/agents/{idOrAddress}/invite-to-chat` does) and sets the\nresulting BotInstance as the App's `defaultBotInstanceId`, the bot that\nanswers website widget visitors. AppId is inferred from the app-token\n(authMw('app')).\n\nThe room is `chatJid` from the body, else the App's bound AI Widget\nchat; it must be one of this App's rooms (else 422\n`CHAT_APP_MISMATCH`). The agent must be owned by the App or be\n`unlisted` / `public`.\n","tags":["Agents"],"operationId":"v2_agents_activate","security":[{"AppJwtAuth":[]}],"parameters":[{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"chatJid":{"type":"string","description":"Room to use instead of the App's AI Widget chat."}}}}}},"responses":{"200":{"description":"```\n{ ok: true, agent: Agent, botInstance: { id, agentId, appId, userId, xmppUsername, status, joinedRooms }, defaultBotInstanceId: string|null }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n{ error: 'Agent is private', code: 'AGENT_PRIVATE' }\n```"},"404":{"description":"`APP_NOT_FOUND` or `AGENT_NOT_FOUND`."},"422":{"description":"The App has no AI Widget chat bound and no `chatJid` was given.\n```\n{ error: 'AI Widget chat not bound; bind one first or pass chatJid', code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"description":"```\n{ error: 'XMPP affiliate/invite failed', code: 'XMPP_UNAVAILABLE' }\n```"}}}},"/v2/apps/{appId}/agents":{"get":{"summary":"List AI Agents (v2, tenant actor)","description":"Same operation and query parameters as `GET /v2/agents`.\nSupports `User JWT` and `B2B Server JWT`.\n\nLists the agents whose home App (`ownerAppId`) is the App in the path,\nwithin the usual visibility rules (the caller's own plus public ones).\nThe caller must manage that App (or belong to it).\n","tags":["Agents"],"operationId":"v2_apps_agents_list","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"query","name":"visibility","required":false,"schema":{"type":"string","enum":["public","mine","all","private"]}},{"in":"query","name":"appId","description":"Only agents whose home App (`ownerAppId`) is this App.","required":false,"schema":{"type":"string"}},{"in":"query","name":"category","required":false,"schema":{"type":"string","enum":["customer-support","persona","entertainment","worker"]}},{"in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"in":"query","name":"offset","required":false,"schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"```\n{ ok: true, total: number, items: [Agent], results: [Agent], pagination: { total: number } }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"summary":"Create an AI Agent for an app (v2, tenant actor)","description":"Same body and response as `POST /v2/agents`, with the App in the path\nas the agent's home App (`ownerAppId`) unless the body names another.\nThe caller must manage that App (403 `OWNER_APP_FORBIDDEN` otherwise).\nSupports `User JWT` and `B2B Server JWT`; with a B2B Server JWT the\nowner is the token's tenant App. Like `POST /v2/agents` it requires the\n`ai` license feature and the account's agents capability.\n","tags":["Agents"],"operationId":"v2_apps_agents_create","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","description":"Same fields as `POST /v2/agents`.","additionalProperties":true}}}},"responses":{"201":{"description":"```\n{ ok: true, agent: Agent }\n```"},"400":{"description":"```\n{ error: 'Invalid flows script', code: 'FLOWS_INVALID', details: [{ path, message, line? }] }\n```"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n{ error: 'You do not manage the App named as ownerAppId', code: 'OWNER_APP_FORBIDDEN' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/apps/{appId}/agents/{idOrAddress}":{"delete":{"summary":"Delete an AI Agent that belongs to an app (v2, tenant actor)","description":"Deletes an agent owned by the App's side: the App's creator, the App\nitself, or its parent tenant App (agents created with a B2B Server JWT\nare owned by the tenant App). Agents are account-level, so deleting an\nApp never deletes them; this is how automation that creates agents for\nan App cleans up after itself. Any other agent, and platform-owned\nagents, read as 404. Same effects as `DELETE /v2/agents/{idOrAddress}`.\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Agents"],"operationId":"v2_apps_agents_delete","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{ ok: true, knowledgeDeleted?: { sites: number|null, docs: number|null, chunks: number|null } }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The caller does not manage this App.\n```\n{ error: string, code: 'FORBIDDEN' }\n```"},"404":{"description":"`APP_NOT_FOUND`, or `AGENT_NOT_FOUND` (no such agent on this App's side)."},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/apps/{appId}/agents/{idOrAddress}/invite-to-chat":{"post":{"summary":"Add an AI Agent to a chat (v2, tenant actor)","description":"Same operation, body and response as\n`POST /v2/agents/{idOrAddress}/invite-to-chat`.\nSupports `User JWT` and `B2B Server JWT`.\n\nThe target App is the one in the path; an `appId` in the body that\ndiffers from it is a 422.\n","tags":["Agents"],"operationId":"v2_apps_agents_invite_to_chat","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"idOrAddress","description":"Agent id, or the agent's `0x...` address.","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"One of `chatJid` or `chatId` is required.","properties":{"appId":{"type":"string"},"chatJid":{"type":"string"},"chatId":{"type":"string"}}}}}},"responses":{"200":{"description":"```\n{ ok: true, created: boolean, agent: Agent, botInstance: {...}, chatJid: string }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n{ error: 'Agent is private', code: 'AGENT_PRIVATE' }\n```"},"404":{"description":"`AGENT_NOT_FOUND`, `APP_NOT_FOUND`, or `CHAT_NOT_FOUND`."},"422":{"description":"```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"description":"```\n{ error: 'XMPP affiliate/invite failed', code: 'XMPP_UNAVAILABLE' }\n```"}}}},"/v2/bot-instances":{"get":{"summary":"List BotInstances (v2)","description":"Lists BotInstances (an agent's embodiment in one App), most recently\nupdated first, at most 200. Supports `User JWT` and `B2B Server JWT`.\n\nWith no filter the caller's own App is listed. Filters:\n- `agentId`: bots of this agent. The agent must be `public` or\n  `unlisted`, or owned by the caller. For someone else's agent only\n  its bots in Apps the caller created (or, with a B2B Server JWT, in\n  the token's App and its child Apps) are listed.\n- `appId`: bots in this App. Allowed for whoever manages the App (its\n  creator, an admin with `appSettings.read`, a B2B Server JWT for it),\n  users of that App and `isSuperAdmin.read`; 403 otherwise.\n- `chatJid`: only bots currently in this room.\n","tags":["Agents"],"operationId":"v2_bot_instances_list","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"query","name":"appId","required":false,"schema":{"type":"string"}},{"in":"query","name":"agentId","required":false,"schema":{"type":"string"}},{"in":"query","name":"chatJid","description":"Full room JID.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n  ok: true,\n  items: [\n    {\n      id: string,\n      agentId: string,\n      appId: string,\n      userId: string,\n      xmppUsername: string,\n      status: 'on'|'off',\n      joinedRooms: string[],\n      lastActiveAt: ISODate|null,\n      createdAt: ISODate,\n      updatedAt: ISODate\n    }\n  ],\n  results: [...same as items...],\n  pagination: {}\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n{ error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/bot-instances/{id}":{"get":{"summary":"Get a BotInstance (v2)","description":"Returns one BotInstance by id. Readable by the agent's owner, whoever\nmanages the bot's App (its creator, an admin with\n`appSettings.read`, a B2B Server JWT for it) and `isSuperAdmin.read`;\nanyone else gets 404. Supports `User JWT` and `B2B Server JWT`.\n","tags":["Agents"],"operationId":"v2_bot_instances_get","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"id","description":"BotInstance id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{ ok: true, botInstance: { id, agentId, appId, userId, xmppUsername, status, joinedRooms, lastActiveAt, createdAt, updatedAt } }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"```\n{ error: 'BotInstance not found', code: 'BOT_INSTANCE_NOT_FOUND' }\n```"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/bot-instances/{id}/status":{"post":{"summary":"Switch a BotInstance on or off (v2)","description":"Sets the BotInstance's `status`. Allowed for the agent's owner,\nwhoever manages the bot's App (its creator, an admin with\n`appSettings.update`, a B2B Server JWT for it) and\n`isSuperAdmin.write`; anyone else gets 403. An `off` bot stays in its rooms but\ndoes not answer. The AI service is told straight away (best-effort).\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Agents"],"operationId":"v2_bot_instances_status_set","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"id","description":"BotInstance id","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["on","off"]}}}}}},"responses":{"200":{"description":"```\n{ ok: true, botInstance: { id, agentId, appId, userId, xmppUsername, status, joinedRooms, lastActiveAt, createdAt, updatedAt } }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"```\n{ error: 'BotInstance not found', code: 'BOT_INSTANCE_NOT_FOUND' }\n```"},"422":{"description":"```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/apps/{appId}/bot-instances":{"get":{"summary":"List BotInstances (v2, tenant actor)","description":"Same operation and query parameters as `GET /v2/bot-instances`.\nSupports `User JWT` and `B2B Server JWT`.\n\nLists the bots in the App in the path (a `?appId=` that differs from\nit is a 422), with the access rule of the `appId` filter above.\n","tags":["Agents"],"operationId":"v2_apps_bot_instances_list","security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"query","name":"appId","required":false,"schema":{"type":"string"}},{"in":"query","name":"agentId","required":false,"schema":{"type":"string"}},{"in":"query","name":"chatJid","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{ ok: true, items: [BotInstance], results: [BotInstance], pagination: {} }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n{ error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/apps/purge-jobs/{jobId}":{"get":{"summary":"Poll the status of a hard-delete cascade purge job (v2)","description":"Returns the Bull job state for a purge initiated via\n`DELETE /v2/apps/{appId}?mode=hard`. Possible states: `waiting`,\n`active`, `completed`, `failed`. On `completed`, `result.summary`\ncontains per-collection deletion counts (users, chats, messages,\nfiles, MinIO objects, etc.) for audit. On `failed`, `error` holds\nthe worker's failure reason.\n\nJobs are retained for 24h after completion/failure.\n\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Apps"],"operationId":"v2_apps_purge_job_status","parameters":[{"in":"path","name":"jobId","description":"Job ID returned by `DELETE /v2/apps/{appId}?mode=hard`. Polled to\ntrack cascade-purge progress.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"job status"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"job not found (expired or never existed)"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/apps/{appId}/tokens":{"get":{"summary":"List app tokens (metadata only) (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nLists app-token metadata for an app.\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Apps"],"operationId":"v2_apps_tokens_list","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppTokensListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]},"post":{"summary":"Create a new app token (returns token once) (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nCreates a new app token for an app and returns the token once.\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Apps"],"operationId":"v2_apps_tokens_create","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","description":"Optional label for the token"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppTokenCreateResponse"}}}},"201":{"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v2/apps/{appId}/tokens/{tokenId}":{"delete":{"summary":"Revoke an app token (idempotent) (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nRevokes an app token by tokenId. Supports `User JWT` and `B2B Server JWT`.","tags":["Apps"],"operationId":"v2_apps_tokens_revoke","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"tokenId","description":"Token ID returned when the token was created (or in `GET\n/v2/apps/{appId}/tokens`). Revokes the token immediately;\nexisting sessions using it 401 on next call.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppTokenRevokeResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v2/apps/{appId}/tokens/{tokenId}/rotate":{"post":{"summary":"Rotate an app token (revoke old, create new) (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nRevokes the old token (best-effort) and returns a new token once.\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Apps"],"operationId":"v2_apps_tokens_rotate","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"tokenId","description":"Token ID to rotate. The existing secret is replaced; the response\ncarries the new JWT.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"description":"Optional human-readable label for the rotated token (e.g.\n`prod-2026-07`). Stored on the token record.\n","type":"string"}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppTokenRotateResponse"}}}},"201":{"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v2/apps/{appId}/provision":{"post":{"summary":"Provision default rooms for an app (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nCreates default/pinned rooms for an app (best-effort XMPP room creation).\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Apps"],"operationId":"v2_apps_provision","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rooms":{"description":"Array of `{ name, title, pinned? }` describing default\nrooms to create for the app. Each entry results in one\nMongo Chat row + one MUC room on the XMPP server.\n","type":"array","maxItems":20,"items":{"type":"object","required":["title"],"properties":{"title":{"type":"string"},"pinned":{"type":"boolean","default":false}}}}}}}}},"responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppProvisionResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v1/apps":{"post":{"description":"**Deprecated**: use `/v2/apps` instead.\n\n**Auth**: User JWT\n\nCreate an application.\n","tags":["Apps"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["displayName"],"properties":{"displayName":{"description":"Human-readable app name shown in the admin panel, sign-up\nscreens, and chat headers. 3-100 chars.\n","type":"string"},"domainName":{"description":"Subdomain used for the hosted app at\n`<domainName>.<rootDomain>`. Alphanumeric only (no\ndashes); must be unique across the platform.\n","type":"string"},"appDescription":{"description":"Alias of `appTagline` accepted on update endpoints for\nclarity. If both are sent, they end up in the same field.\n","type":"string"},"defaultProfileIsPublic":{"type":"boolean","description":"Default value of `profileIsPublic` for newly-created\nusers in this app. If true, anyone in the app can view\na new user's profile by default; if false, only the\nuser themself + admins.\n"},"defaultAssetsArePublic":{"type":"boolean","description":"Default value of `assetsArePublic` for newly-created\nusers in this app. If true, a new user's owned tokens\n/ assets are visible on their public profile by\ndefault; if false, hidden.\n"},"allowUsersToBecomeTenants":{"type":"boolean","description":"If true, end-users of this app can become tenants\nthemselves (create their own apps, take their account\nindependent of this app). Used for the base app where\nany signup can spin up their own apps. Default false\nfor child apps.\n"},"defaultAccessProfileOpen":{"deprecated":true,"type":"boolean","description":"**Deprecated** - kept as a backward-compatible alias\nfor `defaultProfileIsPublic`. New integrations should\nuse `defaultProfileIsPublic`.\n"},"defaultAccessAssetsOpen":{"deprecated":true,"type":"boolean","description":"**Deprecated** - kept as a backward-compatible alias\nfor `defaultAssetsArePublic`. New integrations should\nuse `defaultAssetsArePublic`.\n"},"usersCanFree":{"deprecated":true,"type":"boolean","description":"**Deprecated** - kept as a backward-compatible alias\nfor `allowUsersToBecomeTenants`. New integrations\nshould use `allowUsersToBecomeTenants`.\n"},"logoImage":{"description":"Main app logo. POST/PUT accept either a file upload\n(multipart) or a URL (JSON). 1:1 ratio recommended,\ndisplayed at 120x120 px in the admin panel.\n","type":"string","format":"binary"},"loginScreenBackgroundImage":{"description":"Optional full-bleed image shown behind the sign-up /\nlogin form. JPG or PNG.\n","type":"string","format":"binary"},"coinLogoImage":{"description":"Optional icon for the app's in-platform coin (next to\n`coinSymbol`).\n","type":"string","format":"binary"},"bundleId":{"description":"iOS/Android bundle identifier in reverse-DNS form (e.g.\n`com.acme.chat`). Required for native push notifications.\n","type":"string"},"primaryColor":{"description":"Brand colour as a 6-digit hex string (e.g. `#0052CD`).\nUsed as the accent throughout the chat UI.\n","type":"string"},"coinSymbol":{"description":"Short ticker for the app's coin (e.g. `ETO`). Used in\ntoken balances and transaction summaries.\n","type":"string"},"coinName":{"description":"Display name for the app's coin (e.g. `Ethora Coin`).\n","type":"string"},"createDefaultChat":{"type":"boolean","default":false,"description":"If true, the app will be seeded with a \"Main chat\"\ndefault chat room available to all users. Used when\ncreating new apps manually from within admin panel.\n"},"platformPushEnabled":{"type":"boolean","description":"Platform-key pushes (the base app's FCM/APNs keys serve\nevery app of the install) are on by default; false\ncreates the app with them switched off, the same switch\nas PUT /v1/push/platform/{appId}. If the push service\ncannot be reached, the app is still created.\n"}}}}}},"responses":{"200":{"description":"```\n{\n  app: {\n    _id: string,\n    displayName: string,\n    domainName: string,\n    creatorId: string,\n    bundleId: string,\n    primaryColor: string,\n    coinSymbol: string,\n    coinName: boolean,\n    defaultAccessProfileOpen: boolean,\n    defaultAccessAssetsOpen: boolean,\n    usersCanFree: boolean,\n    isUserDataEncrypted: boolean,\n    parentAppId: boolean,\n    isAllowedNewAppCreate: boolean,\n    isBaseApp: boolean,\n    defaultRooms: [{jid: string, pinned: boolean}],\n    createdAt: string,\n    updatedAt: string,\n    appToken: string,\n    stats: {\n      totalRegistered: number,\n      recentlyRegistered: number,\n      totalSessions: number,\n      recentlySessions: number,\n      totalApiCalls: number,\n      recentlyApiCalls: number,\n      totalFiles: number,\n      recentlyFiles: number,\n      totalTransactions: number,\n      recentlyTransactions: number,\n      totalIssuance: number,\n      recentlyIssuance: number,\n    }\n  },\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Something wrong with input data (validations error & ACL error)\n```\n {error: string}\n```"},"500":{"description":"Internal server error\n```\n {error: string}\n```"}},"security":[{"UserJwtAuth":[]}],"summary":"Create app","deprecated":true,"operationId":"post_v1_apps"},"get":{"description":"**Deprecated**: use `/v2/apps` instead.\n\n**Auth**: User JWT\n\nReturns the applications visible to the current auth context.\nWith a user access token, the list is filtered by that user's readable ACL entries.\nWith `x-custom-token` server auth, the request runs under the token app context and uses the creator user's ACL for that app.\nParameters should be sent in the query string for GET requests.\nPagination uses `limit` and `offset`, where `offset` is the number of records to skip.","tags":["Apps"],"parameters":[{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","default":10,"minimum":1,"maximum":500}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","default":0,"minimum":0}},{"in":"query","name":"orderBy","description":"Field to sort by. Allowed values are listed in the schema; if\nomitted, the endpoint's default ordering is used.\n","schema":{"type":"string","enum":["displayName","totalRegistered","totalSessions","totalApiCalls","totalFiles","totalTransactions","createdAt","totalChats"]}},{"in":"query","name":"order","description":"Sort direction: `asc` (default) or `desc`.\n","schema":{"type":"string","enum":["asc","desc"]}}],"responses":{"200":{"description":"```\n{\n  apps: [{\n    _id: string,\n    displayName: string,\n    domainName: string,\n    creatorId: string,\n    bundleId: string,\n    primaryColor: string,\n    coinSymbol: string,\n    coinName: boolean,\n    defaultAccessProfileOpen: boolean,\n    defaultAccessAssetsOpen: boolean,\n    usersCanFree: boolean,\n    isUserDataEncrypted: boolean,\n    parentAppId: boolean,\n    isAllowedNewAppCreate: boolean,\n    isBaseApp: boolean,\n    defaultRooms: [{jid: string, pinned: boolean}],\n    createdAt: string,\n    updatedAt: string,\n    appToken: string,\n    stats: {\n      totalRegistered: number,\n      recentlyRegistered: number,\n      totalSessions: number,\n      recentlySessions: number,\n      totalApiCalls: number,\n      recentlyApiCalls: number,\n      totalFiles: number,\n      recentlyFiles: number,\n      totalTransactions: number,\n      recentlyTransactions: number,\n      totalIssuance: number,\n      recentlyIssuance: number,\n    }\n  }],\n  total: number,\n  limit: number,\n  offset: number\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"description":"Internal server error\n```\n {error: string}\n```"}},"security":[{"UserJwtAuth":[]}],"summary":"List accessible apps","deprecated":true,"operationId":"get_v1_apps"}},"/v1/apps/users/":{"get":{"description":"**Auth**: User JWT\n\nApp creation","tags":["Apps"],"parameters":[{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","minimum":0,"default":0}},{"in":"query","name":"orderBy","description":"Field to sort by. Allowed values are listed in the schema; if\nomitted, the endpoint's default ordering is used.\n","schema":{"type":"string","enum":["firsName","lastName","createdAt"]}},{"in":"query","name":"order","description":"Sort direction: `asc` (default) or `desc`.\n","schema":{"type":"string","enum":["asc","desc"]}}],"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Something wrong with input data (validations error & ACL error)\n```\n {error: string}\n```"},"500":{"description":"Internal server error\n```\n {error: string}\n```"}},"security":[{"UserJwtAuth":[]}],"summary":"List app users","operationId":"get_v1_apps_users_"}},"/v1/apps/users/{xmppUsername}":{"get":{"description":"**Auth**: B2B Server JWT\n\nApp creation","tags":["Apps"],"parameters":[{"in":"path","name":"xmppUsername","description":"Single user's JID (`<appId>_<userId>`). Returns the user record\nwithout requiring a Mongo `_id` lookup.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Something wrong with input data (validations error & ACL error)\n```\n {error: string}\n```"},"500":{"description":"Internal server error\n```\n {error: string}\n```"}},"security":[{"B2BServerJwtAuth":[]}],"summary":"Get app user by XMPP username","operationId":"get_v1_apps_users_xmppusername"}},"/v1/apps/{id}":{"put":{"description":"**Auth**: User JWT\n\nApp update","tags":["Apps"],"parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"displayName":{"description":"Human-readable app name shown in the admin panel, sign-up\nscreens, and chat headers. 3-100 chars.\n","type":"string"},"domainName":{"description":"Subdomain used for the hosted app at\n`<domainName>.<rootDomain>`. Alphanumeric only (no\ndashes); must be unique across the platform.\n","type":"string"},"appDescription":{"description":"Alias of `appTagline` accepted on update endpoints for\nclarity. If both are sent, they end up in the same field.\n","type":"string"},"defaultProfileIsPublic":{"type":"boolean","description":"Default value of `profileIsPublic` for newly-created\nusers in this app. If true, anyone in the app can view\na new user's profile by default; if false, only the\nuser themself + admins.\n"},"defaultAssetsArePublic":{"type":"boolean","description":"Default value of `assetsArePublic` for newly-created\nusers in this app. If true, a new user's owned tokens\n/ assets are visible on their public profile by\ndefault; if false, hidden.\n"},"allowUsersToBecomeTenants":{"type":"boolean","description":"If true, end-users of this app can become tenants\nthemselves (create their own apps, take their account\nindependent of this app). Used for the base app where\nany signup can spin up their own apps. Default false\nfor child apps.\n"},"defaultAccessProfileOpen":{"deprecated":true,"type":"boolean","description":"**Deprecated** - kept as a backward-compatible alias\nfor `defaultProfileIsPublic`. New integrations should\nuse `defaultProfileIsPublic`.\n"},"defaultAccessAssetsOpen":{"deprecated":true,"type":"boolean","description":"**Deprecated** - kept as a backward-compatible alias\nfor `defaultAssetsArePublic`. New integrations should\nuse `defaultAssetsArePublic`.\n"},"usersCanFree":{"deprecated":true,"type":"boolean","description":"**Deprecated** - kept as a backward-compatible alias\nfor `allowUsersToBecomeTenants`. New integrations\nshould use `allowUsersToBecomeTenants`.\n"},"loginScreenBackgroundImage":{"description":"Optional full-bleed image shown behind the sign-up /\nlogin form. JPG or PNG.\n","type":"string","format":"binary"},"coinLogoImage":{"description":"Optional icon for the app's in-platform coin (next to\n`coinSymbol`).\n","type":"string","format":"binary"},"googleServicesJson":{"description":"Android `google-services.json` contents (raw JSON or\nbase64). Required for FCM push to Android clients.\n","type":"string","format":"binary"},"GoogleServiceInfoPlist":{"description":"iOS `GoogleService-Info.plist` contents (raw XML or\nbase64). Required for FCM push to iOS clients.\n","type":"string","format":"binary"},"GoogleServiceInfoPlist2":{"description":"Optional second iOS plist for apps that ship two iOS\nbundles (e.g. dev + prod).\n","type":"string","format":"binary"},"primaryColor":{"description":"Brand colour as a 6-digit hex string (e.g. `#0052CD`).\nUsed as the accent throughout the chat UI.\n","type":"string"},"coinSymbol":{"description":"Short ticker for the app's coin (e.g. `ETO`). Used in\ntoken balances and transaction summaries.\n","type":"string"},"coinName":{"description":"Display name for the app's coin (e.g. `Ethora Coin`).\n","type":"string"},"firebaseWebConfigString":{"description":"The full Firebase JS config object as a JSON string. Used\nby the web client for FCM push and optional analytics.\nPaste the contents of the Firebase console's \"Your apps\n-> SDK setup\" panel.\n","type":"string"},"loginBackgroundColor":{"description":"Optional fallback colour shown behind the login form when\n`loginScreenBackgroundImage` is not set. Same hex shape\nas `primaryColor`.\n","type":"string"},"logoImage":{"description":"Main app logo. POST/PUT accept either a file upload\n(multipart) or a URL (JSON). 1:1 ratio recommended,\ndisplayed at 120x120 px in the admin panel.\n","type":"string"},"sublogoImage":{"description":"Optional secondary logo / favicon. Same input shapes as\n`logoImage`.\n","type":"string"},"appTagline":{"description":"Short marketing line shown beneath the display name on\nthe sign-up screen. Free-form.\n","type":"string"},"defaultRooms":{"description":"Array of `{ jid, pinned }` describing the rooms\npre-populated for new users when they first sign in.\n`jid` is the room's MUC JID\n(`<appId>_<chatId>@conference.<xmpp-host>`); `pinned:\ntrue` shows it at the top of the chat list.\n","type":"array","items":{"$ref":"#/components/schemas/DefaultRooms"}},"botStatus":{"description":"`on` to enable the AI bot for this app, `off` to disable.\nWhen off, no LLM calls are made and the bot user does not\nrespond in chats.\n","type":"string"},"botPrompt":{"description":"System prompt prepended to the LLM context on every\nreply. Shapes the bot's persona and behavior; supports\nmarkdown.\n","type":"string"},"botGreetingMessage":{"description":"Optional message the bot posts when first added to a room\nor when a new user joins.\n","type":"string"},"botTrigger":{"description":"When the bot replies. `any_message` (every message in\njoined rooms) or `/bot` (only when a message starts with\n`/bot`).\n","type":"string"},"botChatId":{"description":"Optional chat ID the bot is anchored to. When set, the\nbot only responds in this room rather than every room\nit's been added to.\n","type":"string"},"botFirstName":{"description":"Given name for the bot user. Shown as the message sender\nin the chat-component.\n","type":"string"},"botLastName":{"description":"Family name for the bot user. Shown alongside\n`botFirstName`.\n","type":"string"},"botIsRAG":{"description":"If true, the bot augments LLM context with the app's RAG\nsources (uploaded docs + crawled sites) at reply time.\n","type":"boolean"}}}}}},"responses":{"200":{"description":"```\n{\n  result: {\n    _id: string,\n    displayName: string,\n    domainName: string,\n    creatorId: string,\n    bundleId: string,\n    primaryColor: string,\n    coinSymbol: string,\n    coinName: boolean,\n    defaultAccessProfileOpen: boolean,\n    defaultAccessAssetsOpen: boolean,\n    usersCanFree: boolean,\n    isUserDataEncrypted: boolean,\n    parentAppId: boolean,\n    isAllowedNewAppCreate: boolean,\n    isBaseApp: boolean,\n    defaultRooms: [{jid: string, pinned: boolean}],\n    createdAt: string,\n    updatedAt: string,\n    appToken: string,\n  },\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Something wrong with input data (validations error & ACL error)\n```\n {error: string}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Update app","operationId":"put_v1_apps_id"},"delete":{"description":"**Auth**: User JWT\n\nApp delete","tags":["Apps"],"parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n  ok: boolean\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Something wrong with input data (validations error & ACL error)\n```\n {error: string}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Delete app","operationId":"delete_v1_apps_id"},"get":{"description":"**Auth**: User JWT\n\nAllows to get a list of all applications for which the user has reading permissions.","tags":["Apps"],"parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}},{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","minimum":0,"default":0}},{"in":"query","name":"orderBy","description":"Field to sort by. Allowed values are listed in the schema; if\nomitted, the endpoint's default ordering is used.\n","schema":{"type":"string","enum":["displayName","totalRegistered","totalSessions","totalApiCalls","totalFiles","totalTransactions","createdAt","totalChats"]}},{"in":"query","name":"order","description":"Sort direction: `asc` (default) or `desc`.\n","schema":{"type":"string","enum":["asc","desc"]}}],"responses":{"200":{"description":"```\n{\n  apps: [{\n    _id: string,\n    displayName: string,\n    domainName: string,\n    creatorId: string,\n    bundleId: string,\n    primaryColor: string,\n    coinSymbol: string,\n    coinName: boolean,\n    defaultAccessProfileOpen: boolean,\n    defaultAccessAssetsOpen: boolean,\n    usersCanFree: boolean,\n    isUserDataEncrypted: boolean,\n    parentAppId: boolean,\n    isAllowedNewAppCreate: boolean,\n    isBaseApp: boolean,\n    defaultRooms: [{jid: string, pinned: boolean}],\n    createdAt: string,\n    updatedAt: string,\n    appToken: string,\n    stats: {\n      totalRegistered: number,\n      recentlyRegistered: number,\n      totalSessions: number,\n      recentlySessions: number,\n      totalApiCalls: number,\n      recentlyApiCalls: number,\n      totalFiles: number,\n      recentlyFiles: number,\n      totalTransactions: number,\n      recentlyTransactions: number,\n      totalIssuance: number,\n      recentlyIssuance: number,\n    }\n  }],\n  total: number,\n  limit: number,\n  offset: number\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"description":"Internal server error\n```\n {error: string}\n```"}},"security":[{"UserJwtAuth":[]}],"summary":"Get app","operationId":"get_v1_apps_id"}},"/v1/apps/create-app-chat/{id}":{"post":{"description":"**Auth**: User JWT\n\nApp update","tags":["Apps"],"parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["title","pinned"],"properties":{"title":{"description":"Display title for the new default chat.\n","type":"string"},"pinned":{"description":"If true, the chat is pinned to the top of every user's\nlist by default.\n","type":"boolean"}}}}}},"responses":{"200":{"description":"```\n{\n  result: {\n    title: string,\n    jid: string,\n    creator: string,\n  },\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Something wrong with input data (validations error & ACL error)\n```\n {error: string}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Create default app chat","operationId":"post_v1_apps_create_app_chat_id"}},"/v1/apps/delete-app-chat/{id}":{"delete":{"description":"**Auth**: User JWT\n\nApp update","tags":["Apps"],"parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["chatJid"],"properties":{"chatJid":{"description":"Full XMPP MUC JID for the room, e.g.\n`<appId>_<chatId>@conference.<xmpp-host>`.\n","type":"string"}}}}}},"responses":{"200":{"description":"```\n{\n  result: {\n    title: string,\n    jid: string,\n    creator: string,\n  },\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Something wrong with input data (validations error & ACL error)\n```\n {error: string}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Delete default app chat","operationId":"delete_v1_apps_delete_app_chat_id"}},"/v1/apps/user-defaults/{id}":{"put":{"description":"**Auth**: User JWT\n\nApp update","tags":["Apps"],"parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"defaultProfileIsPublic":{"type":"boolean","description":"Default value of `profileIsPublic` for newly-created\nusers in this app. If true, anyone in the app can view\na new user's profile by default; if false, only the\nuser themself + admins.\n"},"defaultAssetsArePublic":{"type":"boolean","description":"Default value of `assetsArePublic` for newly-created\nusers in this app. If true, a new user's owned tokens\n/ assets are visible on their public profile by\ndefault; if false, hidden.\n"},"allowUsersToBecomeTenants":{"type":"boolean","description":"If true, end-users of this app can become tenants\nthemselves (create their own apps, take their account\nindependent of this app). Used for the base app where\nany signup can spin up their own apps. Default false\nfor child apps.\n"},"defaultAccessProfileOpen":{"deprecated":true,"type":"boolean","description":"**Deprecated** - kept as a backward-compatible alias\nfor `defaultProfileIsPublic`. New integrations should\nuse `defaultProfileIsPublic`.\n"},"defaultAccessAssetsOpen":{"deprecated":true,"type":"boolean","description":"**Deprecated** - kept as a backward-compatible alias\nfor `defaultAssetsArePublic`. New integrations should\nuse `defaultAssetsArePublic`.\n"},"usersCanFree":{"deprecated":true,"type":"boolean","description":"**Deprecated** - kept as a backward-compatible alias\nfor `allowUsersToBecomeTenants`. New integrations\nshould use `allowUsersToBecomeTenants`.\n"},"defaultRooms":{"description":"Array of `{ jid, pinned }` describing the rooms\npre-populated for new users when they first sign in.\n`jid` is the room's MUC JID\n(`<appId>_<chatId>@conference.<xmpp-host>`); `pinned:\ntrue` shows it at the top of the chat list.\n","type":"array","items":{"$ref":"#/components/schemas/DefaultRooms"}}}}}}},"responses":{"200":{"description":"```\n{\n    _id: string,\n    displayName: string,\n    domainName: string,\n    creatorId: string,\n    bundleId: string,\n    primaryColor: string,\n    coinSymbol: string,\n    coinName: boolean,\n    defaultAccessProfileOpen: boolean,\n    defaultAccessAssetsOpen: boolean,\n    usersCanFree: boolean,\n    isUserDataEncrypted: boolean,\n    parentAppId: boolean,\n    isAllowedNewAppCreate: boolean,\n    isBaseApp: boolean,\n    defaultRooms: [{jid: string, pinned: boolean}],\n    createdAt: string,\n    updatedAt: string,\n    appToken: string,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Something wrong with input data (validations error & ACL error)\n```\n {error: string}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Update app user defaults","operationId":"put_v1_apps_user_defaults_id"}},"/v1/apps/check-domain-name":{"post":{"description":"**Auth**: None (public)\n\ncheck domain name","tags":["Apps"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domainName"],"properties":{"domainName":{"description":"Subdomain to check for availability. Returns 200 if free,\n422 if taken.\n","type":"string"}}}}}},"responses":{"200":{"description":"```\n{\n  exists: false,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Something wrong with input data (validations error & ACL error)\n```\n {error: string} | {exists: true}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[],"summary":"Check app domain availability","operationId":"post_v1_apps_check_domain_name"}},"/v1/apps/get-config":{"get":{"description":"**Auth**: None (public)\n\nget app data config","tags":["Apps"],"parameters":[{"in":"query","name":"domainName","description":"Lookup an app's public config by its subdomain. Used by the\nhosted client at first page-load before any login token exists.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n  result: {\n    _id: string,\n    displayName: string,\n    domainName: string,\n    creatorId: string,\n    bundleId: string,\n    primaryColor: string,\n    coinSymbol: string,\n    coinName: boolean,\n    defaultAccessProfileOpen: boolean,\n    defaultAccessAssetsOpen: boolean,\n    usersCanFree: boolean,\n    isUserDataEncrypted: boolean,\n    parentAppId: boolean,\n    isAllowedNewAppCreate: boolean,\n    isBaseApp: boolean,\n    defaultRooms: [{jid: string, pinned: boolean}],\n    createdAt: string,\n    updatedAt: string,\n    appToken: string,\n    xmppHost: string,\n    translateLanguages: string[],\n  },\n}\n```\n\n`xmppHost` is the install's XMPP domain, taken from the backend's\n`XMPP_HOST` env (rendered from `XMPP_DOMAIN` at install time). It is\ninstall-wide rather than per-app, so clients can open the chat\nconnection without a build-time env of their own. Omitted when the\ninstall did not set `XMPP_HOST`.\n\n`translateLanguages` lists the BCP-47 tags the install's separately\ndeployed translation server can translate into (deploy.yml\n`translate.languages`, pushed into the database on every install /\nupdate). Also install-wide rather than per-app. Unlike `xmppHost`\nthe key is always present: `[]` means no translation server is\nconfigured, and an absent key would leave clients guessing between\nthat and an API too old to report it.\n\nThis is NOT `chat.translates` / the `languages` block returned by\nlogin and /me. That one is the set of languages the product's UI\nworks in; this one describes a different deployment's capabilities,\nand the two lists may legitimately differ."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Not found\n```\n {error: string}\n```"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[],"summary":"Get app config by domain","operationId":"get_v1_apps_get_config"}},"/v1/apps/get-default-rooms":{"get":{"description":"**Auth**: User JWT\n\nreturns default rooms for application, appJwt is required","tags":["Apps"],"responses":{"200":{"description":"```\n[{jid: string, pinned: boolean}]\n```","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DefaultRoom"}},"examples":{"default_rooms":{"value":[{"jid":"APP_ID_room1@conference.localhost","pinned":true},{"jid":"APP_ID_room2@conference.localhost","pinned":false}]}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Get current app default rooms","operationId":"get_v1_apps_get_default_rooms"}},"/v1/apps/get-default-rooms/app-id/{appId}":{"get":{"description":"**Auth**: User JWT\n\nreturn default rooms for application, userJwt is required","tags":["Apps"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n[{jid: string, pinned: boolean}]\n```","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DefaultRoom"}},"examples":{"default_rooms":{"value":[{"jid":"APP_ID_room1@conference.localhost","pinned":true}]}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"ACL error\n```\n {error: string}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Get app default rooms by appId","operationId":"get_v1_apps_get_default_rooms_app_id_appid"}},"/v1/apps/rotate-jwt/{id}":{"delete":{"description":"**Auth**: User JWT\n\nrotate app jwt","tags":["Apps"],"parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n  app: {\n    _id: string,\n    displayName: string,\n    domainName: string,\n    creatorId: string,\n    bundleId: string,\n    primaryColor: string,\n    coinSymbol: string,\n    coinName: boolean,\n    defaultAccessProfileOpen: boolean,\n    defaultAccessAssetsOpen: boolean,\n    usersCanFree: boolean,\n    isUserDataEncrypted: boolean,\n    parentAppId: boolean,\n    isAllowedNewAppCreate: boolean,\n    isBaseApp: boolean,\n    defaultRooms: [{jid: string, pinned: boolean}],\n    createdAt: string,\n    updatedAt: string,\n    appToken: string,\n  },\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Not found\n```\n {error: string}\n```"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Rotate app JWT","operationId":"delete_v1_apps_rotate_jwt_id"}},"/v1/apps/graph-statistic/{appId}":{"get":{"description":"**Auth**: User JWT\n\nget app statistic","tags":["Apps"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"query","name":"startDate","description":"Lower bound (inclusive) for the time-series window. ISO-8601\ntimestamp or `YYYY-MM-DD`.\n","schema":{"type":"string","format":"date"}},{"in":"query","name":"endDate","description":"Upper bound (inclusive). Same format as `startDate`.\n","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"```\n{\n    sessionsCount: number,\n    apiCallCount: number,\n    issuanceCount: number,\n    transactionsCount: number,\n    sessions: {x: [number], y: [number]},\n    apiCalls: {x: [number], y: [number]},\n    issuance: {x: [number], y: [number]},\n    transactions: {x: [number], y: [number]},\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"ACL error\n```\n {error: string}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Get app graph statistics","operationId":"get_v1_apps_graph_statistic_appid"}},"/v1/apps/stat-csv":{"get":{"description":"**Auth**: User JWT\n\nGet statistics for the applications the user has access to in CSV format.","tags":["Apps"],"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"description":"Internal server error\n```\n {error: string}\n```"}},"security":[{"UserJwtAuth":[]}],"summary":"Export app statistics CSV","operationId":"get_v1_apps_stat_csv"}},"/v1/files/":{"post":{"description":"**Auth**: User JWT\n\nFiles upload","tags":["Files"],"operationId":"v1_files_upload","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["files"],"properties":{"files":{"description":"One or more files. The endpoint's response lists each\nfile's resulting metadata in the same order.\n","type":"array","items":{"type":"string","format":"binary"}},"clientEncrypted":{"description":"Set to `true` when the uploaded bytes are ciphertext the\nclient produced for an end-to-end encrypted chat. The\npayload is then stored verbatim: no preview/thumbnail is\nrendered, no duration or page-size probe runs, and\n`mimetype` is recorded as declared rather than inferred.\nEchoed back as `clientEncrypted` on each result so the\nclient knows to decrypt before rendering.\n","type":"boolean","default":false}}}}}},"responses":{"200":{"description":"```\n{\n  success: boolean,\n  results: [{\n    userId: string,\n    ownerKey: string,\n    location: string,\n    locationPreview?: string,\n    duration?: string,\n    originalname: string,\n    filename: string,\n    mimetype: string,\n    size: string,\n    expiresAt: string,\n    clientEncrypted: boolean,  // bytes are client-side ciphertext\n    isPrivate: string,\n    _id: string,\n    createdAt: string,\n    updatedAt: string,\n  }],\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FilesUploadResponse"},"examples":{"success":{"value":{"success":true,"results":[{"_id":"65f1a9c8e2b4c10012345678","userId":"65f1a9c8e2b4c10087654321","ownerKey":"user","location":"https://cdn.example.com/uploads/2026/01/file.jpg","locationPreview":"https://cdn.example.com/uploads/2026/01/file_preview.jpg","originalname":"file.jpg","filename":"file_1700000000.jpg","mimetype":"image/jpeg","size":"12345","expiresAt":"2026-12-31T23:59:59.000Z","isPrivate":"false","createdAt":"2026-01-01T12:00:00.000Z","updatedAt":"2026-01-01T12:00:00.000Z"}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"description":"Internal server error\n```\n {error: string}\n```"}},"security":[{"UserJwtAuth":[]}],"summary":"Upload files"},"get":{"description":"**Auth**: User JWT\n\nget files created by user (JWT owner)","tags":["Files"],"operationId":"v1_files_list","parameters":[{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","default":10,"minimum":1,"maximum":500}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","default":0,"minimum":0}}],"responses":{"200":{"description":"```\n{\n  limit: number,\n  offset: number,\n  total: number,\n  items: [{\n    userId: string,\n    ownerKey: string,\n    location: string,\n    locationPreview?: string,\n    duration?: string,\n    originalname: string,\n    filename: string,\n    mimetype: string,\n    size: string,\n    expiresAt: string,\n    isPrivate: string,\n    _id: string,\n    createdAt: string,\n    updatedAt: string,\n  }],\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FilesListResponse"},"examples":{"list":{"value":{"limit":10,"offset":0,"total":1,"items":[{"_id":"65f1a9c8e2b4c10012345678","userId":"65f1a9c8e2b4c10087654321","ownerKey":"user","location":"https://cdn.example.com/uploads/2026/01/file.jpg","originalname":"file.jpg","filename":"file_1700000000.jpg","mimetype":"image/jpeg","size":"12345","expiresAt":"2026-12-31T23:59:59.000Z","isPrivate":"false","createdAt":"2026-01-01T12:00:00.000Z","updatedAt":"2026-01-01T12:00:00.000Z"}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"List files"}},"/v2/files/":{"post":{"description":"**Auth**: User JWT\n\nFiles upload (v2)","tags":["Files"],"operationId":"v2_files_upload","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["files"],"properties":{"files":{"description":"One or more files. The endpoint's response lists each\nfile's resulting metadata in the same order.\n","type":"array","items":{"type":"string","format":"binary"}}}}}}},"responses":{"200":{"description":"```\n{\n  success: boolean,\n  results: [{\n    userId: string,\n    ownerKey: string,\n    location: string,\n    locationPreview?: string,\n    duration?: string,\n    originalname: string,\n    filename: string,\n    mimetype: string,\n    size: string,\n    expiresAt: string,\n    isPrivate: string,\n    _id: string,\n    createdAt: string,\n    updatedAt: string,\n  }],\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"description":"Internal server error\n```\n {error: string}\n```"}},"security":[{"UserJwtAuth":[]}],"summary":"Upload files (v2)"},"get":{"description":"**Auth**: User JWT\n\nGet files created by user (JWT owner) (v2).\nv2 canonical: returns `{ results, pagination }`.\nFor backward compatibility it also includes legacy-friendly keys `{ limit, offset, total, items }`.","tags":["Files"],"operationId":"v2_files_list","parameters":[{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","default":10,"minimum":1,"maximum":500}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","default":0,"minimum":0}}],"responses":{"200":{"description":"```\n{\n  results: [{\n    userId: string,\n    ownerKey: string,\n    location: string,\n    locationPreview?: string,\n    duration?: string,\n    originalname: string,\n    filename: string,\n    mimetype: string,\n    size: string,\n    expiresAt: string,\n    isPrivate: string,\n    _id: string,\n    createdAt: string,\n    updatedAt: string,\n  }],\n  pagination: { limit: number, offset: number, total: number },\n\n  // legacy-friendly keys (still present):\n  limit: number,\n  offset: number,\n  total: number,\n  items: array,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"List files (v2)"}},"/v2/files/secure":{"post":{"description":"**Auth**: User JWT\n\nSecure chat-attachment upload (v2). Stores the file in the private\nsecure bucket and binds it to a chat (`roomName`). Downloads are then\ngated per-fetch by chat membership and served from the secure files\ndomain, so the returned `location` points at `secure-files.<domain>`\n(fetch it with the `fileToken` cookie on web, or `?ft=<fileToken>` on\nnative/embedded clients). The caller must be a member of `chatName`.\nOn a server without a secure files host the route answers\n`503 SECURE_FILES_DISABLED`; upload through `POST /v1/files` instead\n(the public files bucket).","tags":["Files"],"operationId":"v2_files_secure_upload","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["files","chatName"],"properties":{"files":{"description":"One or more files to upload.","type":"array","items":{"type":"string","format":"binary"}},"chatName":{"description":"Internal chat name (`<appId>_<uuid>`) the attachment belongs\nto. The uploader must be a member of this chat; the file is\nbound to it and only members can fetch it afterwards.\n","type":"string"},"clientEncrypted":{"description":"Set to `true` when the uploaded bytes are ciphertext the\nclient produced for an end-to-end encrypted chat. The\npayload is then stored verbatim: no preview/thumbnail is\nrendered, no duration or page-size probe runs, and\n`mimetype` is recorded as declared rather than inferred.\nEchoed back as `clientEncrypted` on each result so the\nclient knows to decrypt before rendering.\n","type":"boolean","default":false}}}}}},"responses":{"200":{"description":"```\n{\n  success: boolean,\n  results: [{\n    userId: string,\n    ownerKey: string,\n    location: string,          // https://secure-files.<domain>/<bucket>/<key>\n    locationPreview?: string,\n    originalname: string,\n    filename: string,\n    mimetype: string,\n    size: number,\n    objectKey: string,\n    roomName: string,          // the chat the file is bound to\n    clientEncrypted: boolean,  // bytes are client-side ciphertext\n    _id: string,\n    createdAt: string,\n    updatedAt: string,\n  }],\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The caller is not a member of `chatName`."},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"`chatName` is missing, or no files were provided."},"500":{"description":"Internal server error"}},"security":[{"UserJwtAuth":[]}],"summary":"Create secure"}},"/v1/files/{id}":{"get":{"description":"**Deprecated**: use `/v2/files/{id}` instead.\n\n**Auth**: User JWT\n\nget file created by user (JWT owner) by id","tags":["Files"],"operationId":"v1_files_getById","parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n    userId: string,\n    ownerKey: string,\n    location: string,\n    locationPreview?: string,\n    duration?: string,\n    originalname: string,\n    filename: string,\n    mimetype: string,\n    size: string,\n    expiresAt: string,\n    isPrivate: string,\n    _id: string,\n    createdAt: string,\n    updatedAt: string,\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileRecord"},"examples":{"file":{"value":{"_id":"65f1a9c8e2b4c10012345678","userId":"65f1a9c8e2b4c10087654321","ownerKey":"user","location":"https://cdn.example.com/uploads/2026/01/file.jpg","originalname":"file.jpg","filename":"file_1700000000.jpg","mimetype":"image/jpeg","size":"12345","expiresAt":"2026-12-31T23:59:59.000Z","isPrivate":"false","createdAt":"2026-01-01T12:00:00.000Z","updatedAt":"2026-01-01T12:00:00.000Z"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"description":"Server side error"}},"security":[{"UserJwtAuth":[]}],"summary":"Get file","deprecated":true},"delete":{"description":"**Deprecated**: use `/v2/files/{id}` instead.\n\n**Auth**: User JWT\n\ndelete created by user (JWT owner) by id","tags":["Files"],"operationId":"v1_files_deleteById","parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n    success: boolean,\n}\n```","content":{"application/json":{"schema":{"type":"object"},"examples":{"deleted":{"value":{"success":true}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"description":"Internal server error"}},"security":[{"UserJwtAuth":[]}],"summary":"Delete file","deprecated":true}},"/v2/files/{id}":{"get":{"description":"**Auth**: User JWT\n\nget file created by user (JWT owner) by id (v2). Returns `{ result }`.","tags":["Files"],"operationId":"v2_files_getById","parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{ result: { _id: string, ... }, success: boolean }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"description":"Server side error"}},"security":[{"UserJwtAuth":[]}],"summary":"Get file (v2)"},"delete":{"description":"**Auth**: User JWT\n\ndelete created by user (JWT owner) by id (v2)","tags":["Files"],"operationId":"v2_files_deleteById","parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n    success: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"description":"Internal server error"}},"security":[{"UserJwtAuth":[]}],"summary":"Delete file (v2)"}},"/v1/users/":{"post":{"summary":"end user' google, facebook, apple, crypto' signature","description":"**Auth**: App JWT\n\nCrate End user endpoint, should be used with Authorization header set to App JWT.\nApp JWT example: {JWT token}\n","tags":["Users"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/UserGoogleAppleFacebookLogin"},{"$ref":"#/components/schemas/SignatureRegistration"}]},"examples":{"social":{"value":{"loginType":"google | facebook | apple","password":"pass","authToken":"token"}},"signature":{"value":{"loginType":"signature","walletAddress":"0xd374e71b0943d2ca83968cb3c41332fc693a7427","signature":"0xc83dcf6b475cf99026505658d87741ce9180cc0b4a4ab78d7044d3b9e9d3fdf153e3e9c9c549c06ca26581cdf5398b485129a64affdcc2357b4de68283fe4a541c","msg":"Register","firsName":"Crypto","lastName":"Man"}}}}}},"responses":{"201":{"description":"```\n{\n  success: boolean,\n  user: {\n    defaultWallet: {\n      walletAddress: string\n    },\n    _id: string,\n    firstName: string,\n    lastName: string,\n    email: string,\n    username: string,\n    tags: string[],\n    profileImage: string,\n    emails: [\n      {\n        loginType: string,\n        email: string,\n        verified: boolean,\n        _id: string\n      }\n    ],\n    appId: string,\n    xmppPassword: string,\n    roles: string[],\n    isProfileOpen: boolean,\n    isAssetsOpen: boolean,\n    isAgreeWithTerms: boolean,\n    homeScreen: string,\n    registrationChannelType: string,\n    createdAt: string,\n    updatedAt: string\n  },\n  msg: string,\n  token: string,\n  refreshToken: string\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { errors: [{msg: string}] }\n```"},"500":{"description":"```\n  {error: string}\n```"}},"security":[{"AppJwtAuth":[]}],"operationId":"post_v1_users_"},"put":{"description":"**Auth**: User JWT\n\nUpdate current users","tags":["Users"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"description":"Optional profile picture upload. Replaces the current\n`profileImage`; clear by sending an empty value.\n","type":"string","format":"binary"},"firstName":{"description":"Given name. Omit to leave unchanged.\n","type":"string"},"lastName":{"description":"Family name. Omit to leave unchanged.\n","type":"string"},"username":{"description":"Display handle. Omit to leave unchanged; must be unique\nwithin the app if changed.\n","type":"string"},"email":{"description":"Primary email address. Changing it triggers\nre-verification on the next login.\n","type":"string"},"phone":{"description":"Phone number, free-form (no normalization on input).\n","type":"string"},"company":{"description":"Free-form company / organisation.\n","type":"string"},"addressLine1":{"description":"Postal address line 1.\n","type":"string"},"addressLine2":{"description":"Postal address line 2.\n","type":"string"},"street":{"description":"Street name.\n","type":"string"},"city":{"description":"City of residence. Free-form string.\n","type":"string"},"country":{"description":"ISO-3166 country code (2 letters) or country name.\n","type":"string"},"zip":{"description":"Postal / ZIP code.\n","type":"string"},"description":{"description":"Short bio shown on the user's profile.\n","type":"string"},"profileIsPublic":{"description":"If true, anyone in the app can view this profile. If\nfalse, only the user themself + admins.\n","type":"boolean"},"assetsArePublic":{"description":"If true, the user's owned tokens / assets are visible\non their public profile. If false, hidden.\n","type":"boolean"},"isProfileOpen":{"deprecated":true,"description":"**Deprecated** - kept as a backward-compatible alias\nfor `profileIsPublic`. New integrations should use\n`profileIsPublic`.\n","type":"boolean"},"isAssetsOpen":{"deprecated":true,"description":"**Deprecated** - kept as a backward-compatible alias\nfor `assetsArePublic`. New integrations should use\n`assetsArePublic`.\n","type":"boolean"}}}}}},"responses":{"200":{"description":"```\n{\n  user: {\n    defaultWallet: {\n      walletAddress: string\n    },\n    isSuperAdmin?: {\n      read: boolean,\n      write: boolean\n    },\n    _id: string,\n    firstName: string,\n    lastName: string,\n    email: string,\n    username: string,\n    tags: string[],\n    profileImage: string,\n    emails: [\n      {\n        loginType: string,\n        email: string,\n        verified: boolean,\n        _id: string\n      }\n    ],\n    appId: string,\n    xmppPassword: string,\n    roles: string[],\n    isProfileOpen: boolean,\n    isAssetsOpen: boolean,\n    isAgreeWithTerms: boolean,\n    homeScreen: string,\n    registrationChannelType: string,\n    createdAt: string,\n    updatedAt: string\n  }\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  {error: string}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Update current user","operationId":"put_v1_users_"}},"/v1/users/sign-up-with-email/":{"post":{"summary":"Users are allowed to register from the application, but they will need an authorization token from the corresponding application.","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"description":"New account's email address. Used for login, email\nverification, and password reset.\n","type":"string"},"firstName":{"description":"Given name shown in chat / profile. Required.\n","type":"string"},"lastName":{"description":"Family name shown in chat / profile. Required.\n","type":"string"},"signupPlan":{"description":"Marketing plan tag captured at signup (`free`,\n`business`, etc.). Free-form, used for analytics only.\n","type":"string","enum":["business","free"]},"utm":{"description":"UTM-style attribution string captured at signup.\nFree-form, used for analytics only.\n","type":"string"},"appId":{"type":"string","description":"Application id used to resolve tenant/app context."}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Auth**: None (appId required; legacy App JWT accepted)\n\nProvide `appId` in the request body to resolve app context. Legacy `Authorization` app token is also accepted for backward compatibility.","security":[],"operationId":"post_v1_users_sign_up_with_email_"}},"/v2/users/sign-up-with-email/":{"post":{"summary":"Users are allowed to register from the application, but they will need an authorization token from the corresponding application.","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"description":"New account's email address. Used for login, email\nverification, and password reset.\n","type":"string"},"firstName":{"description":"Given name shown in chat / profile. Required.\n","type":"string"},"lastName":{"description":"Family name shown in chat / profile. Required.\n","type":"string"},"password":{"description":"Initial account password. 8+ characters recommended.\n","type":"string"},"cfToken":{"description":"Cloudflare Turnstile token from the sign-up form, if the\napp has bot protection enabled. Optional otherwise.\n","type":"string"},"utm":{"description":"UTM-style attribution string captured at signup.\nFree-form, used for analytics only.\n","type":"string"},"appId":{"type":"string","description":"Application id used to resolve tenant/app context."}},"required":["email","firstName","password","cfToken"]}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Auth**: None (appId required; legacy App JWT accepted)\n\nProvide `appId` in the request body to resolve app context. Legacy `Authorization` app token is also accepted for backward compatibility.","security":[],"operationId":"post_v2_users_sign_up_with_email_"}},"/v2/users/login/mfa":{"post":{"summary":"Second login step for accounts with MFA enabled","description":"**Auth**: User JWT\n\nWhen the account has MFA enabled, `POST /v1|v2/users/login-with-email`\nand `POST /v1/users/login` (Google / Facebook / Apple / wallet signature)\nstop after the first factor and answer\n`{ mfaRequired: true, mfaToken, expiresIn: 300, methods: ['totp','backup'] }`\nwithout creating a session. Exchange that token plus a 6-digit\nauthenticator code (or one backup code, `XXXXX-XXXXX`) here; the\nresponse is identical to a normal login (token, refreshToken,\nfileToken, wsToken, user, ...). App context as for login-with-email\n(`appId` in the body or an app JWT).\n","tags":["Users"],"operationId":"v2_users_login_mfa","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mfaToken","code"],"properties":{"appId":{"type":"string"},"mfaToken":{"type":"string","description":"The pending token from login-with-email (valid 5 minutes)."},"code":{"type":"string","description":"6-digit TOTP code, or a backup code."}}}}}},"responses":{"200":{"description":"Same payload as a successful login-with-email."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"`MFA_TOKEN_INVALID` (expired / wrong app), `MFA_CODE_INVALID`\n(wrong, replayed, or already-used backup code), `MFA_NOT_ENABLED`."},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}]}},"/v2/users/me/password":{"post":{"summary":"Change (or set) the logged-in user's password","description":"**Auth**: User JWT\n\nAccounts with a password: requires `currentPassword`, or a `reauth`\n(fresh provider sign-in for the account's own email) in its place,\nfor users who sign in with Google and do not know it. Accounts without\none (Google / Facebook / Apple / wallet sign-ups) SET a password here\ninstead and must prove identity first: `reauth` (a fresh provider\nsign-in whose email must match the account) or, when MFA is on,\n`mfaCode`. Without proof the call answers `409 REAUTH_REQUIRED` with\n`details.methods`. On success every other interactive session of the\nuser is ended (API keys and OAuth grants are kept); the calling\nsession stays valid.\n","tags":["Users"],"operationId":"v2_users_me_password","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["newPassword"],"properties":{"currentPassword":{"type":"string","description":"Required when the account already has a password."},"newPassword":{"type":"string","minLength":8,"maxLength":128},"mfaCode":{"type":"string","description":"Proof for a password-less account with MFA on (TOTP or backup code)."},"reauth":{"type":"object","description":"Proof for a password-less account, a fresh provider sign-in.","properties":{"provider":{"type":"string","enum":["google","facebook","apple"]},"idToken":{"type":"string"},"accessToken":{"type":"string"},"authToken":{"type":"string"}}}}}}}},"responses":{"200":{"description":"`{ success: true, sessionsRevoked: number, passwordSet?: true }`"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"`WRONG_CREDENTIALS`, `REAUTH_FAILED`, `REAUTH_EMAIL_MISMATCH`, `MFA_CODE_INVALID`"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`REAUTH_REQUIRED` with `details.methods` (password-less account, no proof given)"},"422":{"description":"`VALIDATION_ERROR`, `PASSWORD_UNCHANGED`"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}]}},"/v2/users/me/mfa":{"get":{"summary":"MFA status of the logged-in user","tags":["Users"],"operationId":"v2_users_me_mfa_status","responses":{"200":{"description":"```\n{ enabled: boolean, enabledAt: string|null, backupCodesRemaining: number,\n  pendingEnrolment: boolean, hasPassword: boolean,\n  passwordResetEmailAvailable: boolean, setPasswordMethods: string[] }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Auth**: User JWT","security":[{"UserJwtAuth":[]}]}},"/v2/users/me/mfa/totp":{"post":{"summary":"Start TOTP enrolment","description":"Generates a new secret and parks it as *pending*; MFA is not enabled\nuntil `POST /v2/users/me/mfa/totp/confirm` succeeds with a first code\n(within 15 minutes). The secret and the `otpauth://` URI are returned\nonce, for a QR code or manual entry in an authenticator app. Accounts\nwith a password must supply it.\n","tags":["Users"],"operationId":"v2_users_me_mfa_enrol","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"password":{"type":"string"}}}}}},"responses":{"200":{"description":"`{ secret, otpauthUri, issuer, account, digits: 6, period: 30, expiresInSeconds }`"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"`WRONG_CREDENTIALS`"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`MFA_ALREADY_ENABLED`"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"delete":{"summary":"Disable MFA","description":"Requires the password (when the account has one) and a valid TOTP or backup code. Ends every other session.","tags":["Users"],"operationId":"v2_users_me_mfa_disable","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"password":{"type":"string"},"code":{"type":"string"}}}}}},"responses":{"200":{"description":"`{ success: true, enabled: false }`"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"`WRONG_CREDENTIALS`, `MFA_CODE_INVALID`"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`MFA_NOT_ENABLED`"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/users/me/mfa/totp/confirm":{"post":{"summary":"Confirm TOTP enrolment with a first code","description":"Turns MFA on and returns the backup codes **once**. Every other\ninteractive session of the user is ended.\n","tags":["Users"],"operationId":"v2_users_me_mfa_confirm","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string"}}}}}},"responses":{"200":{"description":"`{ success: true, enabled: true, backupCodes: string[], sessionsRevoked }`"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"`MFA_CODE_INVALID`"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`MFA_ALREADY_ENABLED`, `MFA_ENROLMENT_EXPIRED`"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/users/me/mfa/backup-codes":{"post":{"summary":"Regenerate backup codes","description":"Requires a valid TOTP or backup code. The previous set stops working; the new set is returned once.","tags":["Users"],"operationId":"v2_users_me_mfa_backup_codes","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string"}}}}}},"responses":{"200":{"description":"`{ success: true, backupCodes: string[] }`"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/apps/{appId}/users/{userId}/access":{"delete":{"summary":"Remove a user's admin-panel access to an app","description":"Turns every grant on the user's ACL row for this app off; the account,\nits chats and its login are untouched (archive the user for that).\nRefuses the caller's own account and the app owner. A platform super\nadmin target is also demoted, but only when the caller is a super admin\nwith write; otherwise the ACL is revoked and 403\n`SUPERADMIN_DEMOTE_FORBIDDEN` is returned. Tenant-actor authenticated,\nneeds `appUsers.admin` or `appSettings.admin` on the app.\n","tags":["Users"],"operationId":"v2_apps_users_access_revoke","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"string"}},{"in":"path","name":"userId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"`{ success: true, userId, hadAccess: boolean, superAdminCleared: boolean }`"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`CANNOT_REVOKE_SELF`, `OWNER_CANNOT_BE_REVOKED`"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/apps/{appId}/users/{userId}/mfa":{"delete":{"summary":"Reset a user's MFA (owner / admin recovery)","description":"Clears the user's MFA so they can sign in with their password again\n(lost device, no backup codes) and ends all of their sessions.\nTenant-actor authenticated: needs `appUsers.update` or\n`appSettings.update` on the app (or B2B / super admin).\n","tags":["Users"],"operationId":"v2_apps_users_mfa_reset","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"string"}},{"in":"path","name":"userId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"`{ success: true, userId, wasEnabled: boolean, sessionsRevoked: number }`"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`USER_NOT_FOUND` (also when the user belongs to another app)"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v1/users/sign-up-resend-email/":{"post":{"summary":"if user do not receive the email, they can resend it","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"description":"Email address that the verification link should be\nre-sent to.\n","type":"string"},"appId":{"type":"string","description":"Application id used to resolve tenant/app context."}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Auth**: None (appId required; legacy App JWT accepted)\n\nProvide `appId` in the request body to resolve app context. Legacy `Authorization` app token is also accepted for backward compatibility.","security":[],"operationId":"post_v1_users_sign_up_resend_email_"}},"/v1/users/create-with-app-id/{id}":{"post":{"summary":"If the user has the appropriate ACL, they can create other users for the application with the ID passed as a parameter","tags":["Users"],"parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"description":"New user's email address. Must be unique under this app.\n","type":"string"},"firstName":{"description":"Given name shown in chat / profile.\n","type":"string"},"lastName":{"description":"Family name shown in chat / profile.\n","type":"string"},"password":{"description":"Optional. When supplied, it becomes the new user's\n*permanent* password (bcrypt-hashed): they sign in with it\ndirectly and no invite email is sent, since the caller is\ndelivering the credential itself. When omitted, the server\nmints a temporary password, mails it out, and the user\nexchanges it for a permanent one via\n`/v1/users/set-permanent-password-with-temp-password`.\n","type":"string","minLength":6,"maxLength":256}}}}}},"responses":{"200":{"description":"```\n{\n  user: {\n    defaultWallet: {\n      walletAddress: string\n    },\n    _id: string,\n    firstName: string,\n    lastName: string,\n    email: string,\n    username: string,\n    tags: string[],\n    profileImage: string,\n    emails: [\n      {\n        loginType: string,\n        email: string,\n        verified: boolean,\n        _id: string\n      }\n    ],\n    appId: string,\n    xmppPassword: string,\n    roles: string[],\n    isProfileOpen: boolean,\n    isAssetsOpen: boolean,\n    isAgreeWithTerms: boolean,\n    homeScreen: string,\n    registrationChannelType: string,\n    createdAt: string,\n    updatedAt: string\n  },\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Auth**: User JWT","security":[{"UserJwtAuth":[]}],"operationId":"post_v1_users_create_with_app_id_id"}},"/v1/users/create-many-with-app-id/{id}":{"post":{"summary":"If the user has the appropriate ACL, they can create other users for the application with the ID passed as a parameter","tags":["Users"],"parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"usersList":{"description":"Array of users to create. Each entry needs at minimum\n`email`, `firstName`, `lastName`; `password` is optional\n(omitted users get a temporary password by email).\n","type":"array","items":{"$ref":"#/components/schemas/UserData"}}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n  results: [\n     {\n        defaultWallet: {\n          walletAddress: string\n        },\n        _id: string,\n        firstName: string,\n        lastName: string,\n        email: string,\n        username: string,\n        tags: string[],\n        profileImage: string,\n        emails: [\n          {\n            loginType: string,\n            email: string,\n            verified: boolean,\n            _id: string\n          }\n        ],\n        appId: string,\n        xmppPassword: string,\n        roles: string[],\n        isProfileOpen: boolean,\n        isAssetsOpen: boolean,\n        isAgreeWithTerms: boolean,\n        homeScreen: string,\n        registrationChannelType: string,\n        createdAt: string,\n        updatedAt: string\n      },\n    }\n  ]\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Auth**: User JWT","security":[{"UserJwtAuth":[]}],"operationId":"post_v1_users_create_many_with_app_id_id"}},"/v1/users/batch":{"post":{"summary":"create user with x-custom-token","tags":["Users"],"parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"bypassEmailConfirmation":{"description":"If true, skip the email-verification step and create\nusers immediately. Use when the importer has already\nverified ownership of the addresses (e.g. SSO migration).\nDefault false.\n","type":"boolean"},"usersList":{"description":"Array of users to create. Each entry needs at minimum\n`email`, `firstName`, `lastName`; `password` is optional\n(omitted users get a temporary password by email).\n","type":"array","items":{"$ref":"#/components/schemas/UserDataB2B"}}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n  results: [\n     {\n        defaultWallet: {\n          walletAddress: string\n        },\n        _id: string,\n        firstName: string,\n        lastName: string,\n        email: string,\n        username: string,\n        tags: string[],\n        profileImage: string,\n        emails: [\n          {\n            loginType: string,\n            email: string,\n            verified: boolean,\n            _id: string\n          }\n        ],\n        appId: string,\n        xmppPassword: string,\n        roles: string[],\n        isProfileOpen: boolean,\n        isAssetsOpen: boolean,\n        isAgreeWithTerms: boolean,\n        homeScreen: string,\n        registrationChannelType: string,\n        createdAt: string,\n        updatedAt: string\n      },\n    }\n  ]\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Deprecated**: use `/v2/users/batch` instead.\n\n**Auth**: User JWT","security":[{"UserJwtAuth":[]}],"deprecated":true,"operationId":"post_v1_users_batch"},"delete":{"description":"**Auth**: User JWT\n\ndeleting user by user ids with x-custom-token","tags":["Users"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["usersIdList"],"properties":{"usersIdList":{"description":"Array of User IDs to operate on. Users must belong to the\ntarget App.\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"```\n  { ok: true }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Delete users batch","operationId":"delete_v1_users_batch"}},"/v2/users/batch":{"post":{"tags":["Users"],"summary":"Async batch user creation (enterprise-friendly)","description":"**Auth**: User JWT\n\nAccepts up to 100 users and enqueues an async job. Returns 202 + jobId immediately to avoid timeouts.\nPoll GET /v2/users/batch/{jobId} for job status and per-user results.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["usersList"],"properties":{"bypassEmailConfirmation":{"description":"If true, skip the email-verification step and create\nusers immediately. Use when the importer has already\nverified ownership of the addresses (e.g. SSO migration).\nDefault false.\n","type":"boolean"},"usersList":{"description":"Array of users to create. Each entry needs at minimum\n`email`, `firstName`, `lastName`; `password` is optional\n(omitted users get a temporary password by email).\n","type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/UserDataB2B"}}}}},"multipart/form-data":{"schema":{"type":"object","required":["usersList"],"properties":{"bypassEmailConfirmation":{"type":"boolean","description":"Can be passed as string (\"true\"/\"false\") in multipart."},"usersList":{"type":"string","description":"JSON-encoded array of users. Each item may include profileImageFileIndex to reference an uploaded file."},"files":{"description":"One or more files. The endpoint's response lists each\nfile's resulting metadata in the same order.\n","type":"array","items":{"type":"string","format":"binary"}}}}}}},"responses":{"202":{"description":"```\n{ ok: true, jobId: string, statusUrl: string }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Validation error"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"post_v2_users_batch"}},"/v2/users/batch/{jobId}":{"get":{"tags":["Users"],"summary":"Get async batch job status/results","parameters":[{"in":"path","name":"jobId","description":"Job ID returned by the corresponding `POST /v2/users/batch` call.\nPolled to track progress.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n  ok: true,\n  jobId: string,\n  state: string,\n  progress: { processed: number, total: number } | number,\n  result?: {\n    ok: true,\n    total: number,\n    processed: number,\n    results: [{ email, uuid?, status: 'created'|'exists'|'uuid_exists'|'failed', userId?, error? }]\n  }\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Job not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Auth**: User JWT","security":[{"UserJwtAuth":[]}],"operationId":"get_v2_users_batch_jobid"}},"/v2/apps/{appId}/users/batch":{"post":{"tags":["Users"],"summary":"Async batch user creation for a target app (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nSame async user batch creation flow as `/v2/users/batch`, but the target app is explicit in the path.\nSupports `User JWT` and `B2B Server JWT`.","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["usersList"],"properties":{"bypassEmailConfirmation":{"description":"If true, skip the email-verification step and create\nusers immediately. Use when the importer has already\nverified ownership of the addresses (e.g. SSO migration).\nDefault false.\n","type":"boolean"},"usersList":{"description":"Array of users to create. Each entry needs at minimum\n`email`, `firstName`, `lastName`; `password` is optional\n(omitted users get a temporary password by email).\n","type":"array","items":{"$ref":"#/components/schemas/UserDataB2B"}}}}},"multipart/form-data":{"schema":{"type":"object","properties":{"bypassEmailConfirmation":{"description":"If true, skip the email-verification step and create\nusers immediately. Use when the importer has already\nverified ownership of the addresses (e.g. SSO migration).\nDefault false.\n","type":"boolean"},"usersList":{"description":"Array of users to create. Each entry needs at minimum\n`email`, `firstName`, `lastName`; `password` is optional\n(omitted users get a temporary password by email).\n","type":"string"},"files":{"description":"One or more files. The endpoint's response lists each\nfile's resulting metadata in the same order.\n","type":"array","items":{"type":"string","format":"binary"}}}}}}},"responses":{"202":{"description":"accepted"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"operationId":"post_v2_apps_appid_users_batch"},"delete":{"tags":["Users"],"summary":"Archive (soft-delete) or purge users in a target app (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nDefault behavior is **soft archive**: each user's `status` flips to\n`archived`, login is blocked, and all related data (wallets, files,\nchat memberships, XMPP account) is retained for restore via\n`POST /v2/apps/{appId}/users/{userId}/restore`.\n\nPass `?mode=hard` for a **synchronous cascade purge** (irreversible):\nper user, XMPP unregister, MinIO blob removal, wallets, files, docs,\nmemberships, ACL, refresh tokens, sessions, and any rooms the user\ncreated (room destroy + chat row + messages + members of that room).\n\nReturns per-user results so the caller can act on partial failures.\n\nSupports `User JWT` and `B2B Server JWT`.","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"query","name":"mode","schema":{"type":"string","enum":["soft","hard"],"default":"soft"},"description":"`soft` (default): reversible archive. `hard`: full cascade purge."},{"in":"query","name":"reason","schema":{"type":"string","maxLength":500},"description":"Optional human-readable explanation stored with the archive."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["usersIdList"],"properties":{"usersIdList":{"description":"Array of User IDs to operate on. Users must belong to the\ntarget App.\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"ok (per-user results in `results[]`)"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"operationId":"delete_v2_apps_appid_users_batch"}},"/v2/apps/{appId}/users/{userId}/restore":{"post":{"tags":["Users"],"summary":"Restore a soft-archived user (v2, tenant actor)","description":"Flips an archived user back to `active`. Login (v1+v2) immediately\nstarts accepting their credentials again. Idempotent for users\nalready active.\n\nCannot restore users mid-purge (`status: 'deleting'`) or fully purged\n(`status: 'deleted'`) - returns 409.\n\nSupports `User JWT` and `B2B Server JWT`.","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"userId","description":"Target User ID, scoped to the `appId` in the same path.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"restored ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"user not found in this app"},"409":{"description":"user is in a non-restorable state"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"post_v2_apps_appid_users_userid_restore"}},"/v2/apps/{appId}/users/batch/{jobId}":{"get":{"tags":["Users"],"summary":"Get app-scoped batch user job status/results (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nReturns status and results for an app-scoped asynchronous user batch job.\nSupports `User JWT` and `B2B Server JWT`.","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"jobId","description":"Job ID returned by the corresponding `POST\n/v2/apps/{appId}/users/batch` call. Polled to track progress.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Job not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"operationId":"get_v2_apps_appid_users_batch_jobid"}},"/v1/users/tags/{appId}":{"get":{"summary":"Tags in use on an app's users, with counts","description":"**Auth**: User JWT\n\nEvery tag carried by at least one active, human user of the app and\nhow many users carry it, most used first (up to 500 tags). Feeds the\ntag filter (`GET /v1/users/{appId}?tag=`) and tag suggestions in the\nadmin panel. Same authorisation as the users list (`appUsers.read`).\n","tags":["Users"],"operationId":"v1_users_tags_list","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"`{ items: [{ tag: string, count: number }], total: number }`"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`FORBIDDEN`"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}]}},"/v1/users/tags-set/{appId}":{"post":{"summary":"Set tags to the users. To preserve the previous user tags, they need to be sent along with the new tags.","description":"**Auth**: User JWT\n\nSet tags to the users. To preserve the previous user tags, they need to be sent along with the new tags. Allows do delete tags with [] tagsList.","tags":["Users"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"usersIdList":{"description":"Array of User IDs to operate on. Users must belong to the\ntarget App.\n","type":"array","items":{"type":"string"}},"tagsList":{"description":"Array of tag strings to apply, add, or remove (depending\non the endpoint).\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"post_v1_users_tags_set_appid"}},"/v2/apps/{appId}/bot":{"get":{"summary":"Get AI bot settings for an app (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nSame operation as `/v2/bot`, but with explicit app targeting.\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Apps"],"operationId":"v2_apps_bot_get","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]},"put":{"summary":"Update AI bot settings for an app (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nSame operation as `/v2/bot`, but with explicit app targeting.\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Apps"],"operationId":"v2_apps_bot_put","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"description":"`on` to enable the AI bot for this app, `off` to\ndisable. When off, no LLM calls are made and the bot\nuser does not respond in chats.\n","type":"string","enum":["on","off"]},"trigger":{"description":"When the bot replies. `any_message` (every message in\njoined rooms) or `/bot` (only when a message starts\nwith `/bot`).\n","type":"string","enum":["any_message","/bot"]},"prompt":{"description":"System prompt prepended to the LLM context on every\nreply. Shapes the bot's persona and behavior; supports\nmarkdown.\n","type":"string"},"greetingMessage":{"description":"Optional message the bot posts when first added to a\nroom or when a new user joins.\n","type":"string"},"chatId":{"description":"Optional chat ID the bot is anchored to. When set, the\nbot only responds in this room rather than every room\nit's been added to.\n","type":"string"},"isRAG":{"description":"If true, the bot augments LLM context with the app's\nRAG sources (uploaded docs + crawled sites) at reply\ntime.\n","type":"boolean"},"botFirstName":{"description":"Given name for the bot user. Shown as the message\nsender in the chat-component.\n","type":"string"},"botLastName":{"description":"Family name for the bot user. Shown alongside\n`botFirstName`.\n","type":"string"},"savedAgentId":{"type":"string"},"ragTags":{"type":"array","maxItems":50,"items":{"type":"string"}},"llmProvider":{"type":"string"},"llmModel":{"type":"string"},"widgetPublicEnabled":{"type":"boolean"},"widgetPublicUrl":{"type":"string"},"botDisplayName":{"type":"string","description":"Empty means unchanged."},"botAvatarUrl":{"type":"string"},"defaultBotInstanceId":{"description":"Which BotInstance answers the website widget. Must be one of\nthis App's BotInstances (422 otherwise); \"\" clears it.\n","type":"string"}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v2/chats/broadcast":{"post":{"tags":["Chats"],"summary":"Async broadcast announcement to selected chat rooms","description":"**Auth**: User JWT\n\nEnqueues a background job to send a system message stanza to each target room (avoids timeouts).\nProvide chatNames (internal Ethora chat names) or chatIds (uuid-only, will be prefixed with appId),\nor set allRooms=true to broadcast to every chat in the app.\n\nAuth model (privileged — app-wide broadcast):\n- Requires a `B2B Server JWT` (`x-custom-token`) for the app, OR a user\n  JWT whose ACL on the app grants `application.appSettings.update` (admin).\n- A plain `App JWT` (shipped in client config) is NOT accepted — it is\n  rejected so end users cannot broadcast app-wide.\n- A non-admin user JWT is rejected with 403.\n- Prefer the explicit `/v2/apps/{appId}/chats/broadcast` for backend or\n  tenant-admin integrations.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"description":"Message body to broadcast. Plain text; chat-component\nrenders it as a system or regular message depending on\n`isSystemMessage`.\n","type":"string"},"allRooms":{"description":"If true, broadcast to every chat in this app. Capped by\n`CHATS_BROADCAST_MAX_ROOMS` (default 5000). Mutually\nexclusive with `chatNames` / `chatIds`.\n","type":"boolean"},"chatNames":{"description":"Array of internal chat names (`<appId>_<chatId>`) to\ntarget. Use this for the most reliable matching. Max 100\nper call.\n","type":"array","maxItems":100,"items":{"type":"string"}},"chatIds":{"description":"Array of `chatId` values (the part after `<appId>_`).\nConvenience shorthand; converted to `chatNames`\nserver-side. Max 100 per call.\n","type":"array","maxItems":100,"items":{"type":"string"}},"metadata":{"description":"Free-form JSON attached to each emitted message. Useful\nfor partner integrations that need a correlation ID.\n","type":"object"},"dryRun":{"description":"If true, validate + resolve the room list and return the\nsame `jobId`/`statusUrl` shape as a real call, but do not\nsend any messages. Use to preview the target set before\ncommitting.\n","type":"boolean"},"sender":{"type":"object","description":"Optional per-broadcast sender override stamped on the outgoing\n`<data>` element (`fullName / senderFirstName / senderLastName /\nphoto`). Falls back to `app.broadcastSender` and then\n`app.displayName` when omitted. Without any of these,\nchat-component renders broadcasts as \"Deleted User\".\nProvide either `name` (split on first whitespace) or explicit\n`firstName` + `lastName`.","properties":{"name":{"type":"string","maxLength":120,"example":"Acme Health"},"firstName":{"type":"string","maxLength":60},"lastName":{"type":"string","maxLength":60},"photoUrl":{"type":"string","format":"uri","example":"https://cdn.example.com/logo.png"}}},"isSystemMessage":{"type":"boolean","default":false,"description":"When true, chat-component renders the broadcast via its SystemMessage component (banner). Defaults to false so broadcasts look like regular messages from the configured sender."}}}}}},"responses":{"202":{"description":"```\n{ ok: true, jobId: string, statusUrl: string }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"post_v2_chats_broadcast"}},"/v2/chats/broadcast/{jobId}":{"get":{"tags":["Chats"],"summary":"Get broadcast job status/results","description":"**Auth**: User JWT\n\nPrivileged, same as the send route: requires a `B2B Server JWT`, or a\nuser JWT with `application.appSettings.read` on the app. A plain `App JWT`\nor a non-admin user JWT is rejected.","parameters":[{"in":"path","name":"jobId","description":"Job ID returned by the corresponding broadcast call. Polled to\ntrack per-room delivery results.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n  ok: true,\n  jobId: string,\n  state: string,\n  progress: { processed: number, total: number } | number,\n  result?: {\n    ok: true,\n    total: number,\n    processed: number,\n    results: [{ chatName, roomJid?, status: 'sent'|'room_not_found'|'dry_run'|'failed', error? }]\n  }\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Job not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"get_v2_chats_broadcast_jobid"}},"/v2/apps/{appId}/chats/broadcast":{"post":{"tags":["Chats"],"summary":"Async broadcast announcement to selected chat rooms in an app (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nSame operation as `/v2/chats/broadcast`, but the target app is explicit in the path.\nThis is the preferred route for backend / tenant-admin integrations.\n\nRequest body shape is identical to `/v2/chats/broadcast` (validated by the same\nJoi schema in the backend), and the response envelope is identical too —\n`{ ok: true, jobId, statusUrl }` returned with HTTP 202. The only differences\nfrom the legacy route are auth model and how the target app is resolved\n(path parameter + ACL check here vs. derived from the token there).\n\nAuth model:\n- `B2B Server JWT`: preferred as `Authorization: Bearer <JWT>`.\n- `B2B Server JWT`: legacy `x-custom-token: <JWT>` is also accepted for backward compatibility.\n- `User JWT`: `Authorization: Bearer <JWT>` is also accepted when the caller has access to the target app.\n\nProvide chatNames (internal Ethora chat names, e.g. `<appId>_<uuid>`) or\nchatIds (uuid-only, will be prefixed with `<appId>_` server-side), or set\nallRooms=true to broadcast to every chat in the app. At least one of\nchatNames / chatIds / allRooms is required.","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"description":"Message body to broadcast. Plain text; chat-component\nrenders it as a system or regular message depending on\n`isSystemMessage`.\n","type":"string"},"allRooms":{"description":"If true, broadcast to every chat in this app. Capped by\n`CHATS_BROADCAST_MAX_ROOMS` (default 5000). Mutually\nexclusive with `chatNames` / `chatIds`.\n","type":"boolean"},"chatNames":{"description":"Array of internal chat names (`<appId>_<chatId>`) to\ntarget. Use this for the most reliable matching. Max 100\nper call.\n","type":"array","maxItems":100,"items":{"type":"string"}},"chatIds":{"description":"Array of `chatId` values (the part after `<appId>_`).\nConvenience shorthand; converted to `chatNames`\nserver-side. Max 100 per call.\n","type":"array","maxItems":100,"items":{"type":"string"}},"metadata":{"description":"Free-form JSON attached to each emitted message. Useful\nfor partner integrations that need a correlation ID.\n","type":"object"},"dryRun":{"description":"If true, validate + resolve the room list and return the\nsame `jobId`/`statusUrl` shape as a real call, but do not\nsend any messages. Use to preview the target set before\ncommitting.\n","type":"boolean"},"sender":{"type":"object","description":"Optional per-broadcast sender override stamped on the outgoing\n`<data>` element (`fullName / senderFirstName / senderLastName /\nphoto`). Falls back to `app.broadcastSender` and then\n`app.displayName` when omitted. Without any of these,\nchat-component renders broadcasts as \"Deleted User\".\nProvide either `name` (split on first whitespace) or explicit\n`firstName` + `lastName`.","properties":{"name":{"type":"string","maxLength":120,"example":"Acme Health"},"firstName":{"type":"string","maxLength":60},"lastName":{"type":"string","maxLength":60},"photoUrl":{"type":"string","format":"uri","example":"https://cdn.example.com/logo.png"}}},"isSystemMessage":{"type":"boolean","default":false,"description":"When true, chat-component renders the broadcast via its SystemMessage component (banner). Defaults to false so broadcasts look like regular messages from the configured sender."}}}}}},"responses":{"202":{"description":"```\n{ ok: true, jobId: string, statusUrl: string }\n```\nstatusUrl is `/v2/apps/{appId}/chats/broadcast/{jobId}` — poll the\nmatching app-scoped job route, not `/v2/chats/broadcast/{jobId}`."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"operationId":"post_v2_apps_appid_chats_broadcast"}},"/v2/apps/{appId}/chats/broadcast/{jobId}":{"get":{"tags":["Chats"],"summary":"Get app-scoped broadcast job status/results (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nSame response shape as `/v2/chats/broadcast/{jobId}`. Cross-app jobId\nprobing is rejected with 403 (the job's recorded appId must match the\napp in the path).","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"jobId","description":"Job ID returned by the corresponding broadcast call. Polled to\ntrack per-room delivery results.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n  ok: true,\n  jobId: string,\n  state: string,\n  progress: { processed: number, total: number } | number,\n  result?: {\n    ok: true,\n    total: number,\n    processed: number,\n    results: [{ chatName, roomJid?, status: 'sent'|'room_not_found'|'dry_run'|'failed', error? }]\n  }\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden — jobId belongs to a different app"},"404":{"description":"Job not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"operationId":"get_v2_apps_appid_chats_broadcast_jobid"}},"/v1/users/tags-add/{appId}":{"post":{"summary":"Add tags to the users.","description":"**Auth**: User JWT\n\nAdd tags to the users.","tags":["Users"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"usersIdList":{"description":"Array of User IDs to operate on. Users must belong to the\ntarget App.\n","type":"array","items":{"type":"string"}},"tagsList":{"description":"Array of tag strings to apply, add, or remove (depending\non the endpoint).\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"post_v1_users_tags_add_appid"}},"/v1/users/tags-delete/{appId}":{"post":{"summary":"Removing tags specified in the request body from users.","description":"**Auth**: User JWT\n\nRemoving tags specified in the request body from users.","tags":["Users"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"usersIdList":{"description":"Array of User IDs to operate on. Users must belong to the\ntarget App.\n","type":"array","items":{"type":"string"}},"tagsList":{"description":"Array of tag strings to apply, add, or remove (depending\non the endpoint).\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"post_v1_users_tags_delete_appid"}},"/v1/users/terms-and-conditions":{"post":{"summary":"Accept the platform terms and conditions","description":"**Auth**: User JWT\n\nRecord that the calling user has accepted the platform's terms.\nPass `agreedToTerms` (preferred); `isAgreeWithTerms` is accepted as\na deprecated alias for backward compatibility. At least one must\nbe set to `true`; if both are sent, `agreedToTerms` wins.\n","tags":["Users"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"agreedToTerms":{"description":"Must be `true` to accept. Stored on the user record with\nthe timestamp of acceptance.\n","type":"boolean"},"isAgreeWithTerms":{"deprecated":true,"description":"**Deprecated** - kept as a backward-compatible alias for\n`agreedToTerms`. New integrations should use `agreedToTerms`.\n","type":"boolean"},"company":{"description":"Optional company name to record alongside acceptance.\n","type":"string","maxLength":30}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"post_v1_users_terms_and_conditions"}},"/v1/users/login":{"post":{"summary":"end user' login by google, facebook, apple, crypto' signature","description":"**Auth**: None (appId required; legacy App JWT accepted)\n\nEnd user login endpoint, should be used with Authorization header set to App JWT.\nApp JWT example: {JWT token}\n\n\nProvide `appId` in the request body to resolve app context. Legacy `Authorization` app token is also accepted for backward compatibility.","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/UserGoogleAppleFacebookLogin"},{"$ref":"#/components/schemas/SignatureLogin"}]},"examples":{"social":{"value":{"loginType":"google | facebook | apple","password":"pass","authToken":"token"}},"signature":{"value":{"loginType":"signature","walletAddress":"0xd374e71b0943d2ca83968cb3c41332fc693a7427","signature":"0xc83dcf6b475cf99026505658d87741ce9180cc0b4a4ab78d7044d3b9e9d3fdf153e3e9c9c549c06ca26581cdf5398b485129a64affdcc2357b4de68283fe4a541c","msg":"Login"}}}}}},"responses":{"200":{"description":"```\n{\n  success: boolean,\n  user: {\n    defaultWallet: {\n      walletAddress: string\n    },\n    _id: string,\n    firstName: string,\n    lastName: string,\n    email: string,\n    username: string,\n    tags: string[],\n    profileImage: string,\n    emails: [\n      {\n        loginType: string,\n        email: string,\n        verified: boolean,\n        _id: string\n      }\n    ],\n    appId: string,\n    xmppPassword: string,\n    roles: string[],\n    isProfileOpen: boolean,\n    isAssetsOpen: boolean,\n    isAgreeWithTerms: boolean,\n    homeScreen: string,\n    registrationChannelType: string,\n    createdAt: string,\n    updatedAt: string\n  },\n  token: string,\n  refreshToken: string\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginSuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Not found"},"409":{"description":"user did not validate his email in registration step"},"422":{"description":"```\n  { errors: [{msg: string}] }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[],"operationId":"post_v1_users_login"}},"/v1/users/xmpp-token":{"post":{"summary":"Get a fresh XMPP connection token","description":"**Auth**: User JWT\n\nReturns a fresh short-lived JWT (in `xmppPassword`) for the authenticated\nuser to (re)connect to the XMPP server (ejabberd). It is the same value the\nlogin / me responses put in `user.xmppPassword`: the client authenticates to\nejabberd with it as the SASL password — a JWT, not the raw account password.\nCall this on reconnect when the previous token has expired.","tags":["Users"],"responses":{"200":{"description":"```\n{ success: true, xmppPassword: string }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"description":"XMPP token unavailable (server missing XMPP_JWT_SECRET / XMPP_HOST)."}},"security":[{"UserJwtAuth":[]}],"operationId":"post_v1_users_xmpp_token"}},"/v1/users/me":{"get":{"summary":"get user data with user access token","description":"**Auth**: User JWT\n\nget user data with user access token\n","tags":["Users"],"responses":{"200":{"description":"```\n{\n  success: boolean,\n  user: {\n    defaultWallet: {\n      walletAddress: string\n    },\n    _id: string,\n    firstName: string,\n    lastName: string,\n    email: string,\n    username: string,\n    tags: string[],\n    profileImage: string,\n    emails: [\n      {\n        loginType: string,\n        email: string,\n        verified: boolean,\n        _id: string\n      }\n    ],\n    appId: string,\n    xmppPassword: string,\n    roles: string[],\n    isProfileOpen: boolean,\n    isAssetsOpen: boolean,\n    isAgreeWithTerms: boolean,\n    homeScreen: string,\n    registrationChannelType: string,\n    createdAt: string,\n    updatedAt: string\n  },\n  token: string,\n  refreshToken: string\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"get_v1_users_me"}},"/v1/users/login-with-email":{"post":{"summary":"login with email and password","description":"**Auth**: None (appId required; legacy App JWT accepted)\n\nEnd user login endpoint, should be used with Authorization header set to App JWT.\nEthora App JWT example: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7Il9pZCI6IjYxZTU1YzlkOTBlYTk5NTk0YmM3ZTZhMiIsImFwcE5hbWUiOiJFdGhvcmEiLCJhcHBHb29nbGVJZCI6Ijk3MjkzMzQ3MDA1NC1oYnNmMjlvaHBhdG83NnRpbDJqdGY2amdnMWI0Mzc0Yy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImNyZWF0b3JJZCI6IjYyYzNmNGE5M2FkMjcwNjc0YzFmNjJmYiIsImNyZWF0ZWRBdCI6IjIwMjItMDctMDVUMDg6Mjc6MjYuMzM2WiIsIl9fdiI6MH0sImlhdCI6MTY1NzAwOTY1OH0.dnvrO_dQ_2GLyUX-b71CcHFDnphpjeTYOxz6vZ2fsPY\n\n\nProvide `appId` in the request body to resolve app context. Legacy `Authorization` app token is also accepted for backward compatibility.","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"description":"Account email address. Must match an existing user under\nthe calling app context.\n","type":"string"},"password":{"description":"Account password. 8+ characters in practice; weaker\npasswords are rejected with `WRONG_CREDENTIALS`.\n","type":"string"},"appId":{"type":"string","description":"Application id used to resolve tenant/app context."}}}}}},"responses":{"200":{"description":"```\n{\n  success: boolean,\n  user: {\n    defaultWallet: {\n      walletAddress: string\n    },\n    isSuperAdmin: {\n      read: boolean,\n      write: boolean\n    },\n    _id: string,\n    firstName: string,\n    lastName: string,\n    email: string,\n    username: string,\n    tags: string[],\n    profileImage: string,\n    emails: [\n      {\n        loginType: string,\n        email: string,\n        verified: boolean,\n        _id: string\n      }\n    ],\n    appId: string,\n    xmppPassword: string,\n    roles: string[],\n    isProfileOpen: boolean,\n    isAssetsOpen: boolean,\n    isAgreeWithTerms: boolean,\n    homeScreen: string,\n    registrationChannelType: string,\n    createdAt: string,\n    updatedAt: string\n  },\n  token: string,\n  refreshToken: string\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginSuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[],"operationId":"post_v1_users_login_with_email"}},"/v1/users/logout":{"post":{"summary":"logout user","tags":["Auth"],"parameters":[{"in":"query","name":"type","description":"Optional logout scope: `current` (this session only, default) or\n`all` (every active session for this user).\n","schema":{"type":"string","enum":["all","current","except-current"]}}],"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Auth**: User JWT","security":[{"UserJwtAuth":[]}],"operationId":"post_v1_users_logout"}},"/v1/users/client":{"post":{"deprecated":true,"summary":"Exchange legacy client JWT for user credentials","description":"**Auth**: Client JWT\n\nLegacy compatibility endpoint for older chat component / mobile SDK flows that still exchange\na `Client JWT` into Ethora user session tokens.\n\nUse `x-custom-token` with payload `data={type: 'client', appId: '', userId: ''}`.\n\nPreferred approach going forward:\n- authenticate the end user via the normal login/signup flows and pass the resulting user tokens directly, or\n- let the embedding app/backend provide the final Ethora user/session object to the chat component instead of relying on this exchange route.\n","tags":["Users"],"responses":{"200":{"description":"```\n{\n  success: boolean,\n  user: {\n    defaultWallet: {\n      walletAddress: string\n    },\n    isSuperAdmin: {\n      read: boolean,\n      write: boolean\n    },\n    _id: string,\n    firstName: string,\n    lastName: string,\n    email: string,\n    username: string,\n    tags: string[],\n    profileImage: string,\n    emails: [\n      {\n        loginType: string,\n        email: string,\n        verified: boolean,\n        _id: string\n      }\n    ],\n    appId: string,\n    xmppPassword: string,\n    roles: string[],\n    isProfileOpen: boolean,\n    isAssetsOpen: boolean,\n    isAgreeWithTerms: boolean,\n    homeScreen: string,\n    registrationChannelType: string,\n    createdAt: string,\n    updatedAt: string\n  },\n  token: string,\n  refreshToken: string\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"ClientJwtAuth":[]}],"operationId":"post_v1_users_client"}},"/v1/users/set-permanent-password-with-temp-password":{"post":{"summary":"set-permanent-password-with-temp-password","tags":["Auth"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tempPassword":{"description":"One-time temporary password issued by the admin (e.g. via\nbatch user import).\n","type":"string"},"password":{"description":"New permanent password to set in place of the temporary\none.\n","type":"string"}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{\n  error: string,\n}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Auth**: None (temp password flow)","operationId":"post_v1_users_set_permanent_password_with_temp_password"}},"/v1/users/login/refresh":{"post":{"description":"**Auth**: Refresh token (JWT)\n\nUser refreshing","tags":["Auth"],"responses":{"200":{"description":"```\n{\n  token: string,\n  refreshToken: string\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTokens"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"RefreshJwtAuth":[]}],"summary":"Refresh access token","operationId":"post_v1_users_login_refresh"}},"/v1/users/changePassword":{"post":{"description":"**Auth**: User JWT\n\nChange the current user's password. Pass `newPassword` (preferred);\n`npassword` is accepted as a deprecated alias for backward\ncompatibility - if both are present, `newPassword` wins.\n","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"password":{"description":"Current account password.\n","type":"string"},"newPassword":{"description":"New password to set. 8+ characters recommended.\n","type":"string"},"npassword":{"deprecated":true,"description":"**Deprecated** - kept as a backward-compatible alias for\n`newPassword`. New integrations should use `newPassword`.\nIf both are sent, `newPassword` wins.\n","type":"string"}}}}}},"responses":{"200":{"description":"```\n{\n  success: boolean,\n  msg: string\n} |\n{\n  success: false,\n  msg: 'Wrong Current Password'\n} |\n{\n  success: false,\n  errors: [{msg: string}]\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Change current user password","operationId":"post_v1_users_changepassword"}},"/v1/users/verifyEmail":{"post":{"description":"**Auth**: User JWT\n\nConfirm the user's email address from a verification link. Pass the\n`token` from the link; nothing else is read from the body.\n","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"description":"One-time email-verification token from the link sent at\nsign-up (or via the resend endpoint). Single use; valid\nfor the lifetime stamped on the link.\n","type":"string"}}}}}},"responses":{"200":{"description":"```\n{\n  success: boolean,\n  msg: string\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{\n  errors: [{msg: string}]\n}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Verify email address","operationId":"post_v1_users_verifyemail"}},"/v1/users/resendLink":{"post":{"description":"**Auth**: User JWT\n\nresend email verification link","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"description":"Email address that the verification link should be\nre-sent to.\n","type":"string"}}}}}},"responses":{"200":{"description":"```\n{\n  success: boolean,\n  msg: string\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"description":"```\n{\n  error: string\n}\n```"}},"security":[{"UserJwtAuth":[]}],"summary":"Resend verification link","operationId":"post_v1_users_resendlink"}},"/v1/users/forgot":{"post":{"description":"**Auth**: None (appId required; legacy App JWT accepted)\n\nrequests a password reset\n\nProvide `appId` in the request body to resolve app context. Legacy `Authorization` app token is also accepted for backward compatibility.","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"description":"Email address to send the password-reset link to.\n","type":"string"},"appId":{"type":"string","description":"Application id used to resolve tenant/app context."}}}}}},"responses":{"200":{"description":"```\n{\n  success: boolean,\n  msg: string\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{\n  error: string\n}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[],"summary":"Start forgot-password flow","operationId":"post_v1_users_forgot"}},"/v1/users/reset":{"post":{"description":"**Auth**: None (reset token in body)\n\nresets a user's password","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"description":"One-time password-reset token from the email link. Valid\nfor 24h.\n","type":"string"}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{\n  error: string\n}\n```"},"500":{"description":"```\n{\n  error: string\n}\n```"}},"security":[],"summary":"Reset password","operationId":"post_v1_users_reset"}},"/v1/users/checkEmail/{email}":{"get":{"description":"**Auth**: None (query appId required; legacy App JWT accepted)\n\nroute to check if particular user exists in all database\n\nProvide `appId` as a query parameter to resolve app context. Legacy `Authorization` app token is also accepted for backward compatibility.","tags":["Users"],"parameters":[{"in":"path","name":"email","description":"Email address to check for availability. Returns 200 if free, 422\nif already in use.\n","required":true,"schema":{"type":"string"}},{"name":"appId","in":"query","required":true,"schema":{"type":"string"},"description":"Application id used to resolve tenant/app context."}],"responses":{"200":{"description":"```\n{\n  success: boolean,\n  msg: string\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{\n  error: string\n}\n```"},"500":{"description":"```\n{\n  error: string\n}\n```"}},"security":[],"summary":"Check email availability","operationId":"get_v1_users_checkemail_email"}},"/v1/users/{appId}":{"get":{"description":"**Auth**: User JWT\n\nget users","tags":["Users"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","minimum":0,"default":0}},{"in":"query","name":"orderBy","description":"Field to sort by (default `createdAt`). `mfaEnabled` sorts on the\nuser's two-step verification status; `authMethod` on the sign-in\nprovider recorded at the last login (legacy users with none sort\nfirst); `tags` puts users without tags first (`asc`) or tagged\nusers first (`desc`); `role` orders members < admins < owner <\nsuper admins (`asc`) or the reverse (`desc`). Ties are broken by\n`_id` so paging is stable.\n","schema":{"type":"string","enum":["email","createdAt","firstName","lastName","authMethod","mfaEnabled","tags","role"]}},{"in":"query","name":"order","description":"Sort direction: `asc` (default) or `desc`.\n","schema":{"type":"string","enum":["asc","desc"]}},{"in":"query","name":"status","description":"`archived` lists only archived users (restore view).","schema":{"type":"string","enum":["archived"]}},{"in":"query","name":"includeArchived","description":"Include archived users alongside active ones.","schema":{"type":"boolean"}},{"in":"query","name":"access","description":"`admin` lists only users who can manage this app in the admin\npanel: any permission over settings, users, tokens, push or\nstats, the app owner, and platform super admins. The \"may create\napps\" grant every base-app signup receives does not count.\n","schema":{"type":"string","enum":["admin"]}},{"in":"query","name":"tag","description":"Only users carrying this tag (case-insensitive, surrounding whitespace ignored). See `GET /v1/users/tags/{appId}` for the tags in use.","schema":{"type":"string"}}],"responses":{"200":{"description":"Paged list. Each item is the user's public record plus `acl` (the\nuser's permission row for this app, when any), `role`\n(`superadmin` | `owner` | `admin` | `member`), `mfaEnabled` and\n`lastSeen`. Credentials (password hash, temporary password, XMPP\npassword, verification tokens, MFA secrets) are never included.\n```\n{ items: [...], total, limit, offset, orderBy, order,\n  ownerUserId, access: 'all'|'admin', tag: string|null }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{\n  error: string\n}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"List users in app","operationId":"get_v1_users_appid"}},"/v1/users/{appId}/{userId}":{"put":{"description":"**Auth**: User JWT\n\nUpdate an end-user belonging to {appId}.\n\nAuth model:\n- `User JWT`: caller must have an AppAcl row for {appId} with\n  `application.appUsers.update == true`. Super-admin write bypasses the Acl.\n- `B2B Server JWT`: allowed when {appId} is the calling tenant's own\n  app or one of its child apps (parentAppId equality). NO Acl lookup.\n\nThe `userId` path slot accepts any of:\n  - Mongo `_id`            (e.g. `699c6923429c2757ac8ab6a4`)\n  - chat `xmppUsername`    (e.g. `<appId>_<id>`)\n  - SDK-facing `uuid`\nWhichever shape the SDK happens to know is fine; the lookup is always\nappId-scoped so it can't cross tenants.","tags":["Users"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"userId","description":"Target User ID, scoped to the `appId` in the same path:\nMongo _id, xmppUsername, or uuid - all appId-scoped.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"description":"Optional profile picture upload. Replaces the current\n`profileImage`; clear by sending an empty value.\n","type":"string","format":"binary"},"firstName":{"description":"Given name. Omit to leave unchanged.\n","type":"string"},"lastName":{"description":"Family name. Omit to leave unchanged.\n","type":"string"},"username":{"description":"Display handle. Omit to leave unchanged; must be unique\nwithin the app if changed.\n","type":"string"},"email":{"description":"Primary email address. Changing it triggers\nre-verification on the next login.\n","type":"string"},"phone":{"description":"Phone number, free-form (no normalization on input).\n","type":"string"},"company":{"description":"Free-form company / organisation.\n","type":"string"},"addressLine1":{"description":"Postal address line 1.\n","type":"string"},"addressLine2":{"description":"Postal address line 2.\n","type":"string"},"street":{"description":"Street name.\n","type":"string"},"city":{"description":"City of residence. Free-form string.\n","type":"string"},"country":{"description":"ISO-3166 country code (2 letters) or country name.\n","type":"string"},"zip":{"description":"Postal / ZIP code.\n","type":"string"},"description":{"description":"Short bio shown on the user's profile.\n","type":"string"},"profileIsPublic":{"description":"If true, anyone in the app can view this profile. If\nfalse, only the user themself + admins.\n","type":"boolean"},"assetsArePublic":{"description":"If true, the user's owned tokens / assets are visible\non their public profile. If false, hidden.\n","type":"boolean"},"isProfileOpen":{"deprecated":true,"description":"**Deprecated** - kept as a backward-compatible alias\nfor `profileIsPublic`. New integrations should use\n`profileIsPublic`.\n","type":"boolean"},"isAssetsOpen":{"deprecated":true,"description":"**Deprecated** - kept as a backward-compatible alias\nfor `assetsArePublic`. New integrations should use\n`assetsArePublic`.\n","type":"boolean"}}}}}},"responses":{"200":{"description":"```\n{\n  user: {\n    defaultWallet: {\n      walletAddress: string\n    },\n    isSuperAdmin?: {\n      read: boolean,\n      write: boolean\n    },\n    _id: string,\n    firstName: string,\n    lastName: string,\n    email: string,\n    username: string,\n    tags: string[],\n    profileImage: string,\n    emails: [\n      {\n        loginType: string,\n        email: string,\n        verified: boolean,\n        _id: string\n      }\n    ],\n    appId: string,\n    xmppPassword: string,\n    roles: string[],\n    isProfileOpen: boolean,\n    isAssetsOpen: boolean,\n    isAgreeWithTerms: boolean,\n    homeScreen: string,\n    registrationChannelType: string,\n    createdAt: string,\n    updatedAt: string\n  }\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{\n  error: string\n}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Update user in app","operationId":"put_v1_users_appid_userid"}},"/v1/users/profile/{walletAddress}":{"get":{"description":"**Auth**: None (public profile)\n\naccess to user profile","tags":["Users"],"parameters":[{"in":"path","name":"walletAddress","description":"Public-key / wallet address of the user whose profile to fetch.\nEVM-style `0x…` for blockchain-enabled apps.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\ntype CoinBalance = {tokenName: string, tokenSymbol: string, balance: number, contractAddress: string},\ntype NFTBalance = {\n  tokenName: string,\n  balance: number,\n  total: number,\n  imagePreview: string,\n  constractTokenIds: [number],\n  nftId: string,\n  tokenType: string,\n  nftFileUrl: string,\n  nftMetaUrl: string,\n  nftOriginalname: string,\n  nftMimetype: string,\n  createdAt: string,\n  updatedAt: string,\n  contractAddress: string,\n},\naxios.response.data = {\n  balances: [CoinBalance | NFTBalance],\n  documents: [],\n  firstName: string,\n  lastName: string,\n  description: string,\n  profileImage: string,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{\n  errors: [{msg: string}]\n}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[],"summary":"Get public profile by wallet","operationId":"get_v1_users_profile_walletaddress"}},"/v1/users/profile/{walletAddress}/{accessToken}":{"get":{"description":"**Auth**: None (public profile)\n\naccess to user profile","tags":["Users"],"parameters":[{"in":"path","name":"walletAddress","description":"Public-key / wallet address of the user whose profile to fetch.\nEVM-style `0x…` for blockchain-enabled apps.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"accessToken","description":"Profile-share token issued by the owner via `/v1/sharelink`.\nBypasses the normal access-grant rules.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\ntype CoinBalance = {tokenName: string, tokenSymbol: string, balance: number, contractAddress: string},\ntype NFTBalance = {\n  tokenName: string,\n  balance: number,\n  total: number,\n  imagePreview: string,\n  constractTokenIds: [number],\n  nftId: string,\n  tokenType: string,\n  nftFileUrl: string,\n  nftMetaUrl: string,\n  nftOriginalname: string,\n  nftMimetype: string,\n  createdAt: string,\n  updatedAt: string,\n  contractAddress: string,\n},\naxios.response.data = {\n  balances: [CoinBalance | NFTBalance],\n  documents: [],\n  firstName: string,\n  lastName: string,\n  description: string,\n  profileImage: string,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{\n  errors: [{msg: string}]\n}\n```"},"498":{"description":"Invalid accessToken"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[],"summary":"Get public profile by wallet and access token","operationId":"get_v1_users_profile_walletaddress_accesstoken"}},"/v1/users/referral":{"post":{"description":"**Auth**: User JWT\n\npost new refferal","tags":["Users"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"referrerId":{"description":"User ID of the inviter. Recorded on the current user's\nrecord; used for referral analytics / rewards.\n","type":"string"}}}}}},"responses":{"200":{"description":"```\n  { success: true }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n{\n  error: string\n}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Create referral","operationId":"post_v1_users_referral"}},"/v1/users":{"delete":{"description":"**Auth**: User JWT\n\ndeleting current user","tags":["Users"],"responses":{"200":{"description":"```\n  { ok: true }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Delete current user","operationId":"delete_v1_users"}},"/v1/users/delete-many-with-app-id/{id}":{"post":{"description":"**Auth**: User JWT\n\ndeleting user by user id","tags":["Users"],"parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["usersIdList"],"properties":{"usersIdList":{"description":"Array of User IDs to operate on. Users must belong to the\ntarget App.\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"```\n  { ok: true }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Delete many users in app","operationId":"post_v1_users_delete_many_with_app_id_id"}},"/v1/users/reset-passwords-with-app-id/{id}":{"post":{"description":"**Auth**: User JWT\n\nreseting password for users from usersIdList","tags":["Users"],"parameters":[{"in":"path","name":"id","description":"ID of the target resource. The exact type (App, File, etc.)\ndepends on the endpoint.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["usersIdList"],"properties":{"usersIdList":{"description":"Array of User IDs to operate on. Users must belong to the\ntarget App.\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"```\n  { ok: true }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Reset many user passwords in app","operationId":"post_v1_users_reset_passwords_with_app_id_id"}},"/v1/users/emails":{"post":{"description":"**Auth**: User JWT\n\nadd emails","tags":["Users"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"loginType":{"description":"Login source for this added email: one of `email`,\n`google`, `facebook`, `apple`, `external`.\n","type":"string"},"email":{"description":"Email address to attach to the calling user's account.\nTriggers a verification email unless `loginType` is an\nOAuth provider.\n","type":"string"}}}}}},"responses":{"200":{"description":"```\n  { success: true }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { errors: [msg: string] }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Create email contact","operationId":"post_v1_users_emails"},"get":{"description":"**Auth**: User JWT\n\nget user emails","tags":["Users"],"responses":{"200":{"description":"```\n  { emails: [{email: string, verified: boolean}] }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"List email contacts","operationId":"get_v1_users_emails"}},"/v1/users/emails/{email}":{"delete":{"description":"**Auth**: User JWT\n\ndeleting user email","tags":["Users"],"parameters":[{"in":"path","name":"email","description":"Email address to detach from the current user's account (one of\n`emails[]`, not the primary `email`).\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n  { success: true }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Delete email contact","operationId":"delete_v1_users_emails_email"}},"/v1/users/payments":{"post":{"description":"**Auth**: User JWT\n\npayments","tags":["Users"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"description":"Payment processor: `stripe`, `apple-iap`, `google-iap`.\nDetermines how `transaction` is interpreted.\n","type":"string"},"transaction":{"description":"Raw transaction payload from the payment processor\n(Stripe PaymentIntent, Apple receipt, Google purchase\ntoken).\n","type":"object"}}}}}},"responses":{"200":{"description":"A successful response"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Create payment record","operationId":"post_v1_users_payments"}},"/v1/users/checkExtWallet":{"post":{"description":"**Auth**: None (public)\n\ncheck if ext wallet exists","tags":["Users"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"walletAddress":{"description":"EVM-style wallet address to check. Returns whether it's\nalready linked to a user in this app.\n","type":"string"}}}}}},"responses":{"200":{"description":"A successful response"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[],"summary":"Check external wallet","operationId":"post_v1_users_checkextwallet"}},"/v1/users/acl":{"get":{"description":"**Auth**: User JWT\n\nget current user' acls","tags":["Users"],"responses":{"200":{"description":"```\n  {\n    result: [{\n      {\n        network: {\n          netStats: {\n            read: boolean,\n            disabled: ['create', 'update', 'delete', 'admin']\n          }\n        },\n        application: {\n          appCreate: {\n            craete: boolean,\n            disabled: ['read', 'update', 'delete', 'admin']\n          },\n          appSettings: {\n            read: boolean,\n            update: boolean,\n            admin: boolean,\n            disabled: ['create', 'delete']\n          },\n          appUsers: {\n            create: boolean,\n            read: boolean,\n            update: boolean,\n            delete: boolean,\n            admin: boolean\n          },\n          appTokens: {\n            create: boolean,\n            read: boolean,\n            update: boolean,\n            admin: boolean,\n            disabled: ['delete']\n          },\n          appPush: {\n            create: boolean,\n            read: boolean,\n            update: boolean,\n            admin: boolean,\n            disabled: ['delete']\n          },\n          appStats: {\n            read: boolean,\n            admin: boolean,\n            disabled: ['create', 'update', 'delete']\n          }\n        }\n      },\n      _id: string,\n      userId: string,\n      appId: string,\n      createdAt: string,\n      updatedAt: string\n    }]\n  }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Get user ACL","operationId":"get_v1_users_acl"}},"/v1/users/acl/{appId}/{userId}":{"put":{"description":"**Auth**: User JWT\n\nupdate acl for appId and userId respectively","tags":["Users"],"parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"userId","description":"Target User ID, scoped to the `appId` in the same path.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"network":{"description":"Network-level ACL flags for this user. Fields: `wallet`,\n`transactions`, `tokens` — each is a `{ read, write }`\nobject. Omit to leave unchanged.\n","type":"object","properties":{"netStats":{"type":"object","properties":{"read":{"type":"boolean"}}}}},"application":{"description":"App-level ACL flags. Top-level keys: `appCreate`,\n`appSettings`, `appUsers`, `wallets`, `marketplace`. Each\nis a fine-grained permission object (`read`, `create`,\n`update`, `delete`, etc.). Omit to leave unchanged.\n","type":"object","properties":{"appCreate":{"type":"object","properties":{"create":{"type":"boolean"}}},"appSettings":{"type":"object","properties":{"read":{"type":"boolean"},"update":{"type":"boolean"},"admin":{"type":"boolean"}}},"appUsers":{"type":"object","properties":{"create":{"type":"boolean"},"read":{"type":"boolean"},"update":{"type":"boolean"},"delete":{"type":"boolean"},"admin":{"type":"boolean"}}},"appTokens":{"type":"object","properties":{"create":{"type":"boolean"},"read":{"type":"boolean"},"update":{"type":"boolean"},"admin":{"type":"boolean"}}},"appPush":{"type":"object","properties":{"create":{"type":"boolean"},"read":{"type":"boolean"},"update":{"type":"boolean"},"admin":{"type":"boolean"}}},"appStats":{"type":"object","properties":{"read":{"type":"boolean"},"admin":{"type":"boolean"}}}}}}}}}},"responses":{"200":{"description":"```\n  {\n    result: {\n      {\n        network: {\n          netStats: {\n            read: boolean,\n            disabled: ['create', 'update', 'delete', 'admin']\n          }\n        },\n        application: {\n          appCreate: {\n            craete: boolean,\n            disabled: ['read', 'update', 'delete', 'admin']\n          },\n          appSettings: {\n            read: boolean,\n            update: boolean,\n            admin: boolean,\n            disabled: ['create', 'delete']\n          },\n          appUsers: {\n            create: boolean,\n            read: boolean,\n            update: boolean,\n            delete: boolean,\n            admin: boolean\n          },\n          appTokens: {\n            create: boolean,\n            read: boolean,\n            update: boolean,\n            admin: boolean,\n            disabled: ['delete']\n          },\n          appPush: {\n            create: boolean,\n            read: boolean,\n            update: boolean,\n            admin: boolean,\n            disabled: ['delete']\n          },\n          appStats: {\n            read: boolean,\n            admin: boolean,\n            disabled: ['create', 'update', 'delete']\n          }\n        }\n      },\n      _id: string,\n      userId: string,\n      appId: string,\n      createdAt: string,\n      updatedAt: string\n    }\n  }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  {error: string}\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Update user ACL","operationId":"put_v1_users_acl_appid_userid"}},"/v1/users/exportData":{"get":{"description":"**Auth**: User JWT\n\nget current user' data as JSON file","tags":["Users"],"responses":{"200":{"description":"```\n  Content-disposition: attachment;filename=exportData.json\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Export current user data","operationId":"get_v1_users_exportdata"}},"/v1/users/export/{appId}":{"get":{"description":"**Auth**: User JWT\n\nexport users","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"tags":["Users"],"responses":{"200":{"description":"```\n  Content-disposition: attachment;filename=exportData.json\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Export app users","operationId":"get_v1_users_export_appid"}},"/v1/sharelink":{"post":{"description":"**Auth**: User JWT\n\nsharelink creation","tags":["Sharelinks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"expiration":{"description":"Unix-epoch timestamp (seconds) when this share link stops\nworking. Omit for a non-expiring link.\n","type":"number"},"memo":{"description":"Free-form description shown to recipients alongside the\nlink. Up to ~500 characters.\n","type":"string"},"resource":{"description":"The thing being shared, as either a chat name\n(`<appId>_<chatId>`) or a fully-qualified MUC JID.\n","type":"string"}}}}}},"responses":{"200":{"description":"```\n   {sharelinkData: {\n     userId: string,\n     walletAddress: string,\n     expiration: string,\n     token: string,\n     targetRecordId: string,\n   }}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SharelinkCreateResponse"},"examples":{"created":{"value":{"sharelinkData":{"userId":"65f1a9c8e2b4c10087654321","walletAddress":"0x0000000000000000000000000000000000000000","expiration":-1,"memo":"no description","resource":"profile","token":"65f1aa1de2b4c10012345681"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Not found"},"422":{"description":"Validations error"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Create share link","operationId":"post_v1_sharelink"}},"/v1/sharelink/":{"get":{"description":"**Auth**: User JWT\n\nDocs get","tags":["Sharelinks"],"responses":{"200":{"description":"```\n   {items: [{\n     userId: string,\n     walletAddress: string,\n     expiration: string,\n     token: string,\n     targetRecordId: string,\n   }]}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SharelinkListResponse"},"examples":{"list":{"value":{"items":[{"userId":"65f1a9c8e2b4c10087654321","walletAddress":"0x0000000000000000000000000000000000000000","expiration":-1,"memo":"no description","resource":"profile","token":"65f1aa1de2b4c10012345681"}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"List share links","operationId":"get_v1_sharelink_"}},"/v1/sharelink/{token}":{"delete":{"description":"**Auth**: User JWT\n\nDocs get","tags":["Sharelinks"],"parameters":[{"in":"path","name":"token","description":"The share-link token returned when the link was created. Anyone\nwho holds this string can access (or revoke) the link.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"A successful response"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Not found"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"summary":"Delete share link","operationId":"delete_v1_sharelink_token"}},"/v1/chats":{"post":{"summary":"Create chat","description":"**Deprecated**: use `/v2/chats` instead.\n\n**Auth**: User JWT\n\nCreate chat","tags":["Chats"],"operationId":"v1_chats_create","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"description":"Display name shown in the chat list and at the top of the\nroom. Free-form.\n","type":"string"},"uuid":{"description":"Optional caller-supplied unique ID for the chat. If\nomitted the server generates one. The final chat name is\n`<appId>_<uuid>`.\n","type":"string"},"description":{"description":"Optional short description shown beneath the title in the\nchat info panel.\n","type":"string"},"type":{"type":"string","description":"public | group"},"picture":{"description":"Optional URL for the room avatar. Typically uploaded via\n`/v1/files/` first; pass the resulting `location` here.\n","type":"string"},"members":{"description":"Array of `xmppUsername` strings (`<appId>_<userId>`) to\nadd as initial members. The caller's own JID is implicit\nand does not need to be listed.\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCreateResponse"},"examples":{"created":{"value":{"result":{"_id":"65f1aa1de2b4c10012345679","name":"APP_ID_65f1aa1de2b4c10012345679","title":"General","description":"General discussion","type":"public","picture":"","appId":"APP_ID","createdBy":"65f1a9c8e2b4c10087654321","createdAt":"2026-01-01T12:00:00.000Z","updatedAt":"2026-01-01T12:00:00.000Z"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"deprecated":true},"get":{"summary":"get chats created by current auth user","description":"**Deprecated**: use `/v2/chats` instead.\n\n**Auth**: User JWT\n\nget chats created by current auth user","tags":["Chats"],"operationId":"v1_chats_list","responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatListResponse"},"examples":{"my_created":{"value":{"results":[{"_id":"65f1aa1de2b4c10012345679","name":"APP_ID_65f1aa1de2b4c10012345679","title":"General","description":"General discussion","type":"public","picture":"","members":[{"xmppUsername":"APP_ID_user1","firstName":"John","lastName":"Doe","profilePicture":"","description":""}]}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"deprecated":true},"delete":{"summary":"Create chat","description":"**Auth**: User JWT\n\nCreate chat","tags":["Chats"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"Chat name (`<appId>_<chatId>`) to delete. The room is\ndestroyed on the XMPP server, the Mongo row removed, and\nall memberships dropped.\n","type":"string"}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"delete_v1_chats"}},"/v2/chats":{"post":{"summary":"Create chat (v2)","description":"**Auth**: User JWT\n\nCreate chat (v2)","tags":["Chats"],"operationId":"v2_chats_create","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"description":"Display name shown in the chat list and at the top of the\nroom. Free-form.\n","type":"string"},"uuid":{"description":"Optional caller-supplied unique ID for the chat. If\nomitted the server generates one. The final chat name is\n`<appId>_<uuid>`.\n","type":"string"},"description":{"description":"Optional short description shown beneath the title in the\nchat info panel.\n","type":"string"},"type":{"type":"string","description":"public | group"},"picture":{"description":"Optional URL for the room avatar. Typically uploaded via\n`/v1/files/` first; pass the resulting `location` here.\n","type":"string"},"members":{"description":"Array of `xmppUsername` strings (`<appId>_<userId>`) to\nadd as initial members. The caller's own JID is implicit\nand does not need to be listed.\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}]},"get":{"summary":"get chats created by current auth user (v2)","description":"**Auth**: User JWT\n\nGet chats created by current auth user (v2).\nSupports pagination via `limit`/`offset`.\nv2 canonical: returns `{ results, pagination }`.\nFor backward compatibility it also includes legacy-friendly keys `{ limit, offset, total, items }`.","tags":["Chats"],"operationId":"v2_chats_list","parameters":[{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","default":50,"minimum":1,"maximum":500}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","default":0,"minimum":0}},{"in":"query","name":"includeMembers","description":"If true, each returned chat carries a `members[]` array of member\nJIDs (and minimal profile info). Default false to keep the list\nresponse small for large rooms.\n","schema":{"type":"boolean","default":true}}],"responses":{"200":{"description":"```\n{\n  results: array,\n  pagination: { limit: number, offset: number, total: number },\n\n  // legacy-friendly keys (still present):\n  limit: number,\n  offset: number,\n  total: number,\n  items: array,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}]}},"/v1/chats/private":{"post":{"summary":"Create private chat","description":"**Auth**: User JWT\n\nCreate chat private chat","tags":["Chats"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"username":{"description":"`xmppUsername` (`<appId>_<userId>`) of the user to start\na 1-1 chat with. Idempotent: if a private chat between\nthe two already exists, that one is returned.\n","type":"string"},"e2ee":{"description":"Ask for the pair's end-to-end encrypted (OMEMO 2) room\nrather than their plaintext one. Defaults to `false`.\n\nA pair can hold both: the encrypted room is a separate\nroom whose `name` carries an `-e2ee` suffix. So calling\nthis with `e2ee: true` for a pair that only has a\nplaintext chat creates the encrypted room alongside it -\nit does not convert the existing one, and the plaintext\nhistory stays where it was written. Idempotent per\nvariant: a second call with the same `e2ee` returns the\nroom already created.\n\nEncrypted rooms stay encrypted for their whole life.\nThere is no endpoint that clears the flag, so neither\nmember can silently downgrade the room under the other.\n\nRequires the app to have `e2eeEnabled: true`\n(`PUT /v1/apps/{appId}`, reported by\n`GET /apps/get-config`). Otherwise this answers\n`403 E2EE_DISABLED`, including for B2B callers - the\nplaintext room for the same pair is still creatable.\n","type":"boolean","default":false}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```","content":{"application/json":{"schema":{"type":"object"},"examples":{"private_chat":{"value":{"result":{"_id":"65f1aa1de2b4c10012345680","type":"private","name":"APP_ID_user1-APP_ID_user2","title":"Jane Doe","appId":"APP_ID","e2ee":false,"members":[{"xmppUsername":"APP_ID_user1","firstName":"John","lastName":"Doe","profilePicture":"","description":""},{"xmppUsername":"APP_ID_user2","firstName":"Jane","lastName":"Doe","profilePicture":"","description":""}]}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```\n\n`code: 'E2EE_DISABLED'` when `e2ee: true` was asked for on an app\nwhose `e2eeEnabled` is off."},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"post_v1_chats_private"}},"/v1/chats/call/create/{chatName}":{"post":{"summary":"Create chat call","description":"**Auth**: User JWT\n\nCreate chat call session","tags":["Chats"],"operationId":"v1_chats_call_create","parameters":[{"in":"path","name":"chatName","required":true,"description":"Chat room name","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n  callId: string\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"description":"```\n  { error: 'Internal server error' }\n```"}},"security":[{"UserJwtAuth":[]}]}},"/v1/chats/call/state/ringing/{callId}":{"post":{"summary":"Update call state to ringing","description":"**Auth**: User JWT\n\nUpdates the chat call state to `ringing`. This allows the user who initiated the call to detect that the recipient is being notified and start playing the ringing tone.","tags":["Chats"],"operationId":"v1_chats_call_state_ringing","parameters":[{"in":"path","name":"callId","schema":{"type":"string"},"required":true,"description":"Unique identifier of the chat call session"}],"responses":{"200":{"description":"```\n{\n  ok: boolean,\n  callId: string\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n{ error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"description":"```\n{ error: 'Internal server error' }\n```"}},"security":[{"UserJwtAuth":[]}]}},"/v1/chats/reports/{chatName}":{"post":{"summary":"Report","tags":["Chats"],"parameters":[{"in":"path","name":"chatName","description":"Internal chat name in the form `<appId>_<chatId>`. Returned by\nevery chat-listing endpoint as `chats[].name`.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"category":{"description":"Report category. Free-form on the API; the admin panel\nsurfaces a fixed picker (`spam`, `harassment`, `other`).\n","type":"string"},"text":{"description":"Reporter-supplied explanation. Shown to moderators\nalongside the report.\n","type":"string"}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Auth**: User JWT","security":[{"UserJwtAuth":[]}],"operationId":"post_v1_chats_reports_chatname"}},"/v2/apps/{appId}/chats":{"get":{"summary":"List all chats in an app (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nLists all chats in the target app.\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Chats"],"operationId":"v2_apps_chats_list","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","default":50,"minimum":1,"maximum":500}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","default":0,"minimum":0}},{"in":"query","name":"includeMembers","description":"If true, each returned chat carries a `members[]` array of member\nJIDs (and minimal profile info). Default false to keep the list\nresponse small for large rooms.\n","schema":{"type":"boolean","default":true}}],"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]},"post":{"summary":"Create a chat in a target app (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nCreates a public or group chat in the explicit target app using the app system account as the room owner.\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Chats"],"operationId":"v2_apps_chats_create","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"description":"Display name shown in the chat list and at the top of the\nroom. Free-form.\n","type":"string"},"uuid":{"description":"Optional caller-supplied unique ID for the chat. If\nomitted the server generates one. The final chat name is\n`<appId>_<uuid>`.\n","type":"string"},"description":{"description":"Optional short description shown beneath the title in the\nchat info panel.\n","type":"string"},"type":{"type":"string","description":"public | group"},"picture":{"description":"Optional URL for the room avatar. Typically uploaded via\n`/v1/files/` first; pass the resulting `location` here.\n","type":"string"},"members":{"description":"Array of `xmppUsername` strings (`<appId>_<userId>`) to\nadd as initial members. The caller's own JID is implicit\nand does not need to be listed.\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]},"delete":{"summary":"Delete a chat in a target app (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nDeletes a chat in the explicit target app by canonical room name.\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Chats"],"operationId":"v2_apps_chats_delete","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Canonical room name / JID local part, usually `{appId}_{roomId}`."}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v2/apps/{appId}/chats/private":{"post":{"summary":"Create a 1-1 chat between two app users (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nOpens a private (1-1) chat between two users of the target app on\nbehalf of an integration backend, without either user being the\ncaller. Supports `B2B Server JWT` and `User JWT`: the app's owner\n(its creator, e.g. from the dashboard app settings page), or an app\nadmin with app-settings update rights.\n\nThe room is the same one `POST /v1/chats/private` addresses for that\npair (name `<xmppUsernameA>-<xmppUsernameB>`, sorted, plus `-e2ee`\nfor the encrypted room), so the call is idempotent: if the room\nalready exists it is returned with `created: false` and status 200.\nA new room answers 201 with `created: true`. The room is owned by\nthe app's system chat account, which also sends both invites.\n","tags":["Chats"],"operationId":"v2_apps_chats_private_create","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to. Both members must belong to it.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["members"],"properties":{"members":{"description":"Exactly two different users, as `xmppUsername`\n(`<appId>_<userId>`) or bare user id - the `<appId>_`\nprefix is added when missing.\n","type":"array","minItems":2,"maxItems":2,"items":{"type":"string"}},"e2ee":{"description":"Address the pair's end-to-end encrypted (OMEMO 2) room\ninstead of the plaintext one. Requires the app's\n`e2eeEnabled` opt-in, otherwise `403 E2EE_DISABLED`.\nEncrypted rooms stay encrypted for life.\n","type":"boolean","default":false}}}}}},"responses":{"200":{"description":"Room already existed (`created: false`)."},"201":{"description":"Room created (`created: true`)."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Not allowed to manage the app, or `E2EE_DISABLED`."},"404":{"description":"`APP_NOT_FOUND` or `USER_NOT_FOUND`."},"422":{"description":"`VALIDATION_ERROR` (not exactly two different users)."},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"description":"`XMPP_ROOM_CREATE_FAILED`."}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v2/apps/{appId}/chats/users-access":{"post":{"summary":"Add chat members in a target app (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nAdds members to an app-scoped chat.\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Chats"],"operationId":"v2_apps_chats_users_access_add","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["chatName","members"],"properties":{"chatName":{"description":"Internal chat name in the form `<appId>_<chatId>`.\nReturned by every chat-listing endpoint as\n`chats[].name`.\n","type":"string"},"members":{"description":"Array of `xmppUsername` strings (`<appId>_<userId>`) to\nadd or remove from the room. Already-member adds and\nnot-yet-member removes are no-ops.\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]},"delete":{"summary":"Remove chat members in a target app (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nRemoves members from an app-scoped chat.\nSupports `User JWT` and `B2B Server JWT`.\n","tags":["Chats"],"operationId":"v2_apps_chats_users_access_remove","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["chatName","members"],"properties":{"chatName":{"description":"Internal chat name in the form `<appId>_<chatId>`.\nReturned by every chat-listing endpoint as\n`chats[].name`.\n","type":"string"},"members":{"description":"Array of `xmppUsername` strings (`<appId>_<userId>`) to\nadd or remove from the room. Already-member adds and\nnot-yet-member removes are no-ops.\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v2/apps/{appId}/users/{userId}/chats":{"get":{"summary":"List chats for target user (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nReturns chat rooms that the specified user belongs to within the target app.\nSupports `User JWT` and `B2B Server JWT` for tenant actors with access to the target app.","tags":["Chats"],"operationId":"v2_apps_users_chats_list","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"userId","description":"Target User ID, scoped to the `appId` in the same path.\n","required":true,"schema":{"type":"string"}},{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","default":50,"minimum":1,"maximum":500}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","default":0,"minimum":0}},{"in":"query","name":"includeMembers","description":"If true, each returned chat carries a `members[]` array of member\nJIDs (and minimal profile info). Default false to keep the list\nresponse small for large rooms.\n","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"```\n{\n  results: array,\n  pagination: { limit: number, offset: number, total: number },\n  limit: number,\n  offset: number,\n  total: number,\n  items: array\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatUserChatsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"description":"```\n  { error: string, code: 'APP_NOT_FOUND'|'USER_NOT_FOUND' }\n```"},"422":{"description":"```\n  { error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v2/apps/{appId}/chats/{chatId}":{"patch":{"summary":"Update chat metadata (title/description) (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nUpdate chat room metadata (Mongo-backed title/description) for tenant actors.\nSupports `User JWT` and `B2B Server JWT`.\nThe `{chatId}` path value must be the canonical roomJid local part (the stored chat name), for example `${appId}_${roomId}`.\nA full room JID like `${appId}_${roomId}@conference.example.com` is also accepted; the domain part is ignored.","tags":["Chats"],"operationId":"v2_apps_chats_patch_meta","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"chatId","required":true,"schema":{"type":"string"},"description":"Canonical roomJid local part / chat name, usually `${appId}_${roomId}`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"description":"New display title. Propagates to the XMPP MUC room\nconfig; live clients see the change next time they fetch\nchat metadata.\n","type":"string"},"description":{"description":"New short description shown in the chat info panel.\n","type":"string"}}}}}},"responses":{"200":{"description":"```\n{\n  ok: true,\n  result: {\n    _id: string,\n    name: string,\n    title: string,\n    description: string,\n    type: string,\n    picture: string,\n    appId: string,\n    updatedAt: any\n  }\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden', code: 'FORBIDDEN' }\n```"},"404":{"description":"```\n  { error: string, code: 'APP_NOT_FOUND'|'CHAT_NOT_FOUND' }\n```"},"422":{"description":"```\n  { error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v1/chats/my":{"get":{"summary":"list current user's chats","description":"**Auth**: User JWT\n\nRetrieves a list of chats current user is a member of, along with the permitted metadata of these chats and their members, to be displayed in client apps.\nEach item carries the caller's own `muted: boolean` for that chat (see `PUT/DELETE /v1/chats/my/{chatName}/mute`).\nPrivate chats also carry `e2ee: boolean` - fixed for the life of the room (see `POST /v1/chats/private`).","tags":["Chats"],"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatMyRoomsResponse"},"examples":{"my_rooms":{"value":{"items":[{"_id":"65f1aa1de2b4c10012345679","name":"APP_ID_65f1aa1de2b4c10012345679","title":"General","description":"General discussion","type":"public","picture":"","reported":false,"members":[{"xmppUsername":"APP_ID_user1","firstName":"John","lastName":"Doe","profilePicture":"","description":""}]}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"get_v1_chats_my"}},"/v1/chats/my/{chatName}":{"get":{"summary":"get my chat by chatName","description":"**Auth**: User JWT\n\nget my chat","tags":["Chats"],"parameters":[{"in":"path","name":"chatName","description":"Internal chat name in the form `<appId>_<chatId>`. Returned by\nevery chat-listing endpoint as `chats[].name`.\n","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatMyRoomResponse"},"examples":{"single_room":{"value":{"result":{"_id":"65f1aa1de2b4c10012345679","name":"APP_ID_65f1aa1de2b4c10012345679","title":"General","description":"General discussion","type":"public","picture":"","members":[{"xmppUsername":"APP_ID_user1","firstName":"John","lastName":"Doe","profilePicture":"","description":""}]}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"get_v1_chats_my_chatname"}},"/v1/chats/my/{chatName}/mute":{"put":{"summary":"Mute a chat for the current user","description":"**Auth**: User JWT\n\nMutes a chat the caller is a member of. The flag is stored on the\ncaller's own membership, so other members are not affected. Idempotent.\nThe state is returned as `muted` on `GET /v1/chats/my` and\n`GET /v1/chats/my/{chatName}`. This is a stored preference only; it does\nnot change push delivery by itself.","tags":["Chats"],"operationId":"v1_chats_my_mute","parameters":[{"in":"path","name":"chatName","description":"Internal chat name, as returned in `items[].name` by `GET /v1/chats/my`.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{ ok: true, result: { chatName: string, muted: true } }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"```\n  { error: string, code: 'CHAT_MEMBERSHIP_NOT_FOUND' }\n```"},"422":{"description":"```\n  { error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}]},"delete":{"summary":"Unmute a chat for the current user","description":"**Auth**: User JWT\n\nClears the mute flag set by `PUT /v1/chats/my/{chatName}/mute`. Idempotent.","tags":["Chats"],"operationId":"v1_chats_my_unmute","parameters":[{"in":"path","name":"chatName","description":"Internal chat name, as returned in `items[].name` by `GET /v1/chats/my`.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"```\n{ ok: true, result: { chatName: string, muted: false } }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"```\n  { error: string, code: 'CHAT_MEMBERSHIP_NOT_FOUND' }\n```"},"422":{"description":"```\n  { error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}]}},"/v1/chats/public":{"get":{"summary":"get public chats for current app","description":"**Auth**: User JWT\n\nget public chats for current app","tags":["Chats"],"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicChatsResponse"},"examples":{"public_rooms":{"value":{"limit":10,"offset":0,"total":1,"items":[{"_id":"65f1aa1de2b4c10012345679","name":"APP_ID_65f1aa1de2b4c10012345679","title":"General","description":"General discussion","type":"public","picture":"","appId":"APP_ID","createdAt":"2026-01-01T12:00:00.000Z","updatedAt":"2026-01-01T12:00:00.000Z"}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"get_v1_chats_public"}},"/v1/chats/users-access":{"post":{"summary":"add user to chat","description":"**Deprecated**: use `/v2/chats/users-access` instead.\n\n**Auth**: User JWT\n\nadd user to chat","tags":["Chats"],"operationId":"v1_chats_usersAccess_add","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"chatName":{"description":"Internal chat name in the form `<appId>_<chatId>`.\nReturned by every chat-listing endpoint as\n`chats[].name`.\n","type":"string"},"members":{"description":"Array of `xmppUsername` strings (`<appId>_<userId>`) to\nadd or remove from the room. Already-member adds and\nnot-yet-member removes are no-ops.\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"deprecated":true},"delete":{"summary":"remove user from chat","description":"**Deprecated**: use `/v2/chats/users-access` instead.\n\n**Auth**: User JWT\n\nremove user from chat","tags":["Chats"],"operationId":"v1_chats_usersAccess_remove","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"chatName":{"description":"Internal chat name in the form `<appId>_<chatId>`.\nReturned by every chat-listing endpoint as\n`chats[].name`.\n","type":"string"},"members":{"description":"Array of `xmppUsername` strings (`<appId>_<userId>`) to\nadd or remove from the room. Already-member adds and\nnot-yet-member removes are no-ops.\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"deprecated":true}},"/v2/chats/users-access":{"post":{"summary":"add user to chat (v2)","description":"**Auth**: User JWT\n\nadd user to chat (v2)","tags":["Chats"],"operationId":"v2_chats_usersAccess_add","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"chatName":{"description":"Internal chat name in the form `<appId>_<chatId>`.\nReturned by every chat-listing endpoint as\n`chats[].name`.\n","type":"string"},"members":{"description":"Array of `xmppUsername` strings (`<appId>_<userId>`) to\nadd or remove from the room. Already-member adds and\nnot-yet-member removes are no-ops.\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"```\n{\n  results: [UserPublic],        // newly-added users only\n  details: [{ xmppUsername, status, code? }], // per-member outcome (idempotent)\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatUsersAccessAddResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}]},"delete":{"summary":"remove user from chat (v2)","description":"**Auth**: User JWT\n\nremove user from chat (v2)","tags":["Chats"],"operationId":"v2_chats_usersAccess_remove","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"chatName":{"description":"Internal chat name in the form `<appId>_<chatId>`.\nReturned by every chat-listing endpoint as\n`chats[].name`.\n","type":"string"},"members":{"description":"Array of `xmppUsername` strings (`<appId>_<userId>`) to\nadd or remove from the room. Already-member adds and\nnot-yet-member removes are no-ops.\n","type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"```\n{\n  results: [string],           // removed xmpp usernames only\n  details: [{ xmppUsername, status, code? }], // per-member outcome (idempotent)\n}\n```","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatUsersAccessRemoveResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}]}},"/v1/chats/users":{"get":{"summary":"Get users / chat members","description":"**Deprecated**: use `/v2/chats/users` instead.\n\n**Auth**: User JWT\n\nReturns users in the current app, or chat members when `chatName` is provided.\nIf `xmppUsername` is provided, returns a single user record.\n","tags":["Chats"],"operationId":"v1_chats_users_get","parameters":[{"in":"query","name":"chatName","description":"Internal chat name in the form `<appId>_<chatId>`. Returned by\nevery chat-listing endpoint as `chats[].name`.\n","schema":{"type":"string"}},{"in":"query","name":"xmppUsername","description":"Filter results to a specific member's JID. When set, the response\ncontains only the row(s) for that user across the listed chats.\n","schema":{"type":"string"}},{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"```\n{ result?: object, results?: array }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"deprecated":true},"patch":{"summary":"Batch update users (chat API)","tags":["Chats"],"operationId":"v1_chats_users_patch","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"users":{"description":"Array of membership-flag patches. Each entry is `{\nchatName, xmppUsername, ...flags }` - `chatName` and\n`xmppUsername` identify the membership row; `flags` (e.g.\n`isNew`, `reported`) update its state. Use\n`/v2/apps/{appId}/chats/users-access` for adding /\nremoving members.\n","type":"array","items":{"type":"object"}}}}}}},"responses":{"200":{"description":"```\n{ results: array }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Deprecated**: use `/v2/chats/users` instead.\n\n**Auth**: User JWT","security":[{"UserJwtAuth":[]}],"deprecated":true}},"/v2/chats/users":{"get":{"summary":"Get users / chat members (v2)","description":"**Auth**: User JWT\n\nReturns users in the current app, or chat members when `chatName` is provided.\nIf `xmppUsername` or `userId` is provided, returns a single user record.\nParameters should be sent in the query string for GET requests.\nPagination uses `limit` and `offset`, where `offset` is the number of records to skip.\n`xmppUsername` is the preferred identifier for single-user lookup.\n`userId` is a backward-compatible identifier matched against Ethora user `uuid` and Mongo `_id`.","tags":["Chats"],"operationId":"v2_chats_users_get","parameters":[{"in":"query","name":"chatName","description":"Internal chat name in the form `<appId>_<chatId>`. Returned by\nevery chat-listing endpoint as `chats[].name`.\n","schema":{"type":"string"}},{"in":"query","name":"xmppUsername","description":"Filter results to a specific member's JID. When set, the response\ncontains only the row(s) for that user across the listed chats.\n","schema":{"type":"string"}},{"in":"query","name":"userId","description":"Target User ID, scoped to the `appId` in the same path.\n","schema":{"type":"string"}},{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","default":100,"minimum":1,"maximum":500}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","default":0,"minimum":0}}],"responses":{"200":{"description":"```\n// list response:\n{ limit, offset, total, items, results, pagination, success }\n\n// single response (when xmppUsername/userId provided):\n{ result, success }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}]},"patch":{"summary":"Batch update users (chat API) (v2)","description":"**Auth**: User JWT\n\nBatch update chat user fields (firstName / lastName / username /\nprofileImage / email).\n\nAuth model: User JWT with `appUsers.update` Acl, or B2B Server JWT\ntargeting the token's own app.\n\nLookup: each item must include `uuid` OR `xmppUsername`. The\n`xmppUsername` (when supplied) MUST start with `<reqUser.appId>_`\n(the calling token's app id). This strict prefix check is intentional\nand catches a common SDK foot-gun: wrappers that double-prefix the\nxmppUsername (e.g. `<appId>_<appId>_<id>`). When acting on a CHILD\napp from a parent-app token, the prefix can never match this route's\nexpectation - prefer the path-explicit\n`PATCH /v2/apps/{appId}/chats/users` (tenant-actor auth) instead.\n\nPer-user statuses (in `results[]` / `details[]`):\n  - `updated`     - row updated, fresh `user` snapshot returned\n  - `not-found`   - no user matched `{uuid|xmppUsername, appId}`\n  - `skipped`     - no updatable fields present in the item\n  - `invalid`     - payload-shape rejection (e.g. xmppUsername\n                    prefix mismatch, email already in use by\n                    another user in this app)\n\nThe HTTP status is always 200 when the request itself was well-\nformed; clients MUST inspect each item's `status` to know if an\nindividual user actually changed.","tags":["Chats"],"operationId":"v2_chats_users_patch","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["users"],"properties":{"users":{"description":"Array of user-record patches. Each item must include\n`uuid` OR `xmppUsername` to identify the target user;\nany remaining keys (`firstName`, `lastName`, `username`,\n`profileImage`, `email`) are applied. The endpoint\nreturns per-item status under `results[]` so a single\nbad row never fails the whole batch.\n","type":"array","minItems":1,"maxItems":100,"items":{"type":"object","description":"Must include `uuid` OR `xmppUsername` (lookup key).","properties":{"uuid":{"type":"string"},"xmppUsername":{"type":"string","example":"<appId>_<userId>"},"firstName":{"type":"string"},"lastName":{"type":"string"},"username":{"type":"string"},"profileImage":{"type":"string","description":"URL"},"email":{"type":"string","format":"email","description":"Validated against the deployment's EMAIL_TLD_VALIDATION\npolicy (off | icann | list); trim/lowercase normalized\nserver-side. Pass an empty string to clear the email.\nReturns `status: 'invalid'` if the new value is\nalready used by another user in the same appId."}}}}}}}}},"responses":{"200":{"description":"```\n{ ok: true, summary, results, details }\n```\nSee description above for the per-user `status` values you must\ninspect to know if individual rows actually changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatUsersPatchBatchResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}]}},"/v2/apps/{appId}/chats/users":{"patch":{"summary":"Batch update users (chat API) (v2, tenant actor)","description":"**Auth**: Tenant Actor\n\nSame operation as `/v2/chats/users` but with the target app explicit\nin the path. This is the preferred route for backend / tenant-admin\nintegrations, especially when:\n\n- You're acting on a CHILD app from a parent-app B2B token. The\n  legacy `/v2/chats/users` route uses `reqUser.appId` for the\n  xmppUsername-prefix check and DB scope, so a parent-app token can\n  never satisfy `xmppUsername.startsWith(<childAppId>_)` for a child\n  app's user. This route uses the path `:appId` instead.\n\n- You want the target appId to be unambiguous in the URL for\n  ops/audit/logging purposes (matches `/v2/apps/{appId}/chats/broadcast`\n  and the rest of the path-explicit tenant-actor surface).\n\nAuth model:\n- `B2B Server JWT`: allowed when `:appId` is the token's own app or\n  one of its child apps (parentAppId equality).\n- `User JWT`: allowed when the caller has an `appUsers.update` Acl\n  on `:appId`.\n\nRequest body shape and response envelope are identical to\n`/v2/chats/users` (same Joi schema, same patchChatUsersBatchService\nunder the hood; only the auth + the `appId` resolution differ).","tags":["Chats"],"operationId":"v2_apps_chats_users_patch","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["users"],"properties":{"users":{"description":"Array of user-record patches. Each item must include\n`uuid` OR `xmppUsername` to identify the target user;\nany remaining keys (`firstName`, `lastName`, `username`,\n`profileImage`, `email`) are applied. The endpoint\nreturns per-item status under `results[]` so a single\nbad row never fails the whole batch.\n","type":"array","minItems":1,"maxItems":100,"items":{"type":"object","description":"Must include `uuid` OR `xmppUsername` (lookup key).","properties":{"uuid":{"type":"string"},"xmppUsername":{"type":"string","example":"<appId>_<userId>"},"firstName":{"type":"string"},"lastName":{"type":"string"},"username":{"type":"string"},"profileImage":{"type":"string","description":"URL"},"email":{"type":"string","format":"email","description":"Validated against the deployment's EMAIL_TLD_VALIDATION\npolicy (off | icann | list); trim/lowercase normalized\nserver-side. Pass an empty string to clear the email.\nReturns `status: 'invalid'` if the new value is\nalready used by another user in the same appId."}}}}}}}}},"responses":{"200":{"description":"```\n{ ok: true, summary, results, details }\n```\nPer-user statuses match `/v2/chats/users` PATCH (`updated` /\n`not-found` / `skipped` / `invalid`); inspect `details[].status`\nper item to know if a row actually changed."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Cross-tenant write denied (token cannot manage :appId)."},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}]}},"/v1/chats/users/{userId}":{"patch":{"summary":"Update single user profile (chat API)","tags":["Chats"],"operationId":"v1_chats_user_patch","parameters":[{"in":"path","name":"userId","description":"Target User ID, scoped to the `appId` in the same path.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"```\n{ result: object }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Deprecated**: use `/v2/chats/users/{userId}` instead.\n\n**Auth**: User JWT","security":[{"UserJwtAuth":[]}],"deprecated":true}},"/v2/chats/users/{userId}":{"patch":{"summary":"Update single user profile (chat API) (v2)","tags":["Chats"],"operationId":"v2_chats_user_patch","parameters":[{"in":"path","name":"userId","description":"Target User ID, scoped to the `appId` in the same path.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"```\n{ result: object }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"description":"**Auth**: User JWT","security":[{"UserJwtAuth":[]}]}},"/v1/chats/media/{chatName}":{"post":{"summary":"Post chat media","description":"**Auth**: User JWT\n\nPost chat media","tags":["Chats"],"parameters":[{"in":"path","name":"chatName","description":"Internal chat name in the form `<appId>_<chatId>`. Returned by\nevery chat-listing endpoint as `chats[].name`.\n","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"files":{"description":"One or more files. The endpoint's response lists each\nfile's resulting metadata in the same order.\n","type":"array","items":{"type":"string","format":"binary"}}}}}}},"responses":{"200":{"description":"```\n{\n  ok: boolean,\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"```\n  { error: 'Forbidden' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"```\n  { error: string }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"security":[{"UserJwtAuth":[]}],"operationId":"post_v1_chats_media_chatname"}},"/v2/widget/sessions":{"post":{"tags":["AI Widget"],"summary":"Provision an AI Widget visitor session","description":"Public, unauthenticated endpoint called by the embedded `ethora-ai-chat-widget`\non mount. Mints (or resumes) an anonymous visitor user in ejabberd, creates a\npersistent MUC room, pre-affiliates the visitor and the App's AI bot as members,\nand returns the credentials needed for the widget to SASL-bind and join.\n\nVisitor JIDs follow the convention `${appId}_widget-<uuid>` so mod_ethora's\nper-app prefix guard accepts them as members of the room. Rooms follow\n`${appId}_<roomUuid>` and are tagged in the platform DB as `chats.type='widget'`.\n\nThe endpoint is rate-limited per IP (30/minute) since it's reachable from any\nbrowser without auth. It validates that the App exists and has an AI bot\nconfigured before provisioning anything.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appId"],"properties":{"appId":{"type":"string","description":"The Ethora App _id whose widget the visitor is opening."},"resumeXmppUsername":{"type":"string","description":"Optional resume hint. When supplied, the server reuses the existing\nvisitor identity instead of minting a new one. The widget reads this\nfrom localStorage so a returning browser keeps its identity (and its\nconversation history is reachable to operators by visitor _id).\nThe value MUST start with `${appId}_widget-` or the request 422s."}}}}}},"responses":{"200":{"description":"```\n{\n  visitor: {\n    xmppUsername: string,   // bare localpart, e.g. \"${appId}_widget-<uuid>\"\n    xmppPassword: string,\n    uuid: string,\n    jid?: string             // fully-qualified, \"<localpart>@<host>\"\n  },\n  room: {\n    name: string,           // bare room name, e.g. \"${appId}_<roomUuid>\"\n    chatId: string,         // Mongo _id of the chats row\n    jid?: string            // fully-qualified, \"<name>@conference.<host>\"\n  },\n  bot: {\n    xmppUsername: string,\n    jid?: string\n  },\n  xmpp?: {\n    host: string,           // ejabberd host, e.g. \"xmpp.example.com\"\n    service: string,        // MUC service, e.g. \"conference.xmpp.example.com\"\n    wsUrl: string           // canonical WS URL, e.g. \"wss://xmpp.example.com/ws\"\n  }\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"App not found.\n```\n{ error: 'App not found', code: 'APP_NOT_FOUND' }\n```"},"409":{"description":"App has no AI bot configured.\n```\n{ error: 'App has no AI bot configured', code: 'AI_BOT_NOT_CONFIGURED' }\n```"},"422":{"description":"Validation error (e.g. missing appId, malformed resumeXmppUsername).\n```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"429":{"description":"Rate limit exceeded (default 30 requests / minute / IP).\n```\n{ error: 'Too many requests', code: 'RATE_LIMITED' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"description":"Underlying ejabberd registration or room-create call failed.\n```\n{ error: string, code: 'XMPP_REGISTER_FAILED' | 'XMPP_ROOM_CREATE_FAILED' }\n```"}},"operationId":"post_v2_widget_sessions"}},"/v2/widget/config":{"get":{"tags":["AI Widget"],"summary":"Public AI Widget config for an app","description":"Public, unauthenticated endpoint the embedded widget calls at load, before it\ndraws the launcher. Returns the appearance the App's admins saved with\n`PUT /v2/apps/{appId}/widget/appearance`.\n\nAn unknown or inactive App returns an empty `appearance` rather than an\nerror, so the endpoint does not reveal which App ids exist. Everything it\nreturns is already public on any page that carries the widget snippet.\n\nResponses carry `Cache-Control: public, max-age=60`, so a change can take a\nminute to reach visitors. Rate-limited per IP (default 300 requests / minute).","operationId":"v2_widget_config_get","security":[],"parameters":[{"in":"query","name":"appId","description":"The Ethora App _id (24 hex characters) whose widget is loading.","required":true,"schema":{"type":"string","pattern":"^[0-9a-fA-F]{24}$"}}],"responses":{"200":{"description":"```\n{\n  ok: true,\n  appearance: { [attribute: string]: string }   // e.g. { 'data-primary-color': '#0052cc' }, {} when none\n}\n```"},"400":{"description":"`appId` missing or not a 24-hex id.\n```\n{ ok: false, error: 'appId required' }\n```"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"description":"Rate limit exceeded.\n```\n{ success: false, error: 'Too many requests', code: 'RATE_LIMITED' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/apps/{appId}/widget/appearance":{"get":{"tags":["AI Widget"],"summary":"Get the stored AI Widget appearance for an app","description":"Returns the website widget appearance stored for the App: a map of the\nwidget's `data-*` attribute names to string values, holding only the\nvalues that differ from the widget's defaults.\n\nAuth: tenantActor, with App settings read access (the App's creator\nalways has it).","operationId":"v2_apps_widget_appearance_get","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"responses":{"200":{"description":"```\n{ ok: true, appearance: { [attribute: string]: string } }   // {} when nothing is stored\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Caller has no access to this app."},"404":{"description":"```\n{ error: 'App not found', code: 'APP_NOT_FOUND' }\n```"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"put":{"tags":["AI Widget"],"summary":"Replace the stored AI Widget appearance for an app","description":"Replaces the stored appearance with `appearance`, a map of `data-*`\nattribute names to values. Send only the values that differ from the\nwidget's defaults; `{}` clears everything. Numbers and booleans are\naccepted and stored as strings; a `null` value drops that attribute.\n\nThe embed reads the stored set at load (through `GET /v2/widget/config`) as\nthe lowest layer: attributes written on the snippet and URL parameters\noverride it.\n\nEvery attribute is checked against an allowlist, and the whole request is\nrejected (422) if any name is unknown or any value invalid:\n- text: `data-title`, `data-greeting-title` (120 chars), `data-greeting`\n  (500), `data-greeting-message` (1000), `data-cta-text` (160); no control\n  characters.\n- colors (hex, rgb/rgba/hsl/hsla, or a color name): `data-primary-color`,\n  `data-secondary-color`, `data-icons-color`, `data-own-bubble-bg`,\n  `data-other-bubble-bg`, `data-input-bg`.\n- `data-launcher-gradient`: two colors separated by a comma.\n- sizes (number with px, %, vw, vh, svh, dvh, rem or em, or a `calc()`):\n  `data-font-size`, `data-width`, `data-height`, `data-expanded-width`,\n  `data-expanded-height`, `data-expanded-inset`.\n- booleans (`true`/`false`, `1`/`0`, `yes`/`no`, stored as `true` or\n  `false`): `data-hide-system-messages`, `data-disable-media`,\n  `data-allow-fullscreen`, `data-start-fullscreen`, `data-flat-launcher`,\n  `data-launcher-glow`, `data-cta-sparkle`.\n- integers: `data-launcher-size` (24 to 160), `data-cta-delay` (0 to 600000).\n- `data-position`: `left` or `right`.\n- `data-locale`: a language tag such as `en` or `pt-BR`.\n- `data-font-family`: letters, digits, spaces, commas, quotes, `_` and `-`\n  (120 chars).\n- `data-google-font`: a Google Fonts family spec (120 chars).\n- `data-launcher-icon`: an http(s) URL.\n\nIdentity attributes (`data-app-id`, `data-api-base`, `data-bot-id`) cannot be\nstored: a saved setting never repoints the widget at another App, bot or\nbackend.\n\nAuth: tenantActor, with App settings update access (the App's creator\nalways has it).","operationId":"v2_apps_widget_appearance_put","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}}],"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appearance"],"properties":{"appearance":{"type":"object","additionalProperties":{"oneOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"example":{"data-primary-color":"#0052cc","data-position":"left","data-greeting":"Hi! How can we help?"}}}}}}},"responses":{"200":{"description":"The stored set, values normalised.\n```\n{ ok: true, appearance: { [attribute: string]: string } }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Caller has no access to this app."},"404":{"description":"```\n{ error: 'App not found', code: 'APP_NOT_FOUND' }\n```"},"422":{"description":"`appearance` missing or not an object, or attributes that are unknown or\nhold invalid values (all named in the message).\n```\n{ error: 'Invalid widget appearance: data-foo (not a widget appearance attribute), ...', code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/apps/{appId}/widget/conversations":{"get":{"tags":["AI Widget"],"summary":"List AI Widget conversations for an app","description":"Returns paginated rows from the `chats` collection where `type='widget'` and\n`appId` matches. Each row includes resolved visitor identity (joined via\n`user_to_chats` -> users where `isVisitor:true`) so the admin Widget\nConversations panel can render visitor handle + first-seen timestamps\nwithout an additional round-trip per row.\n\nAuth: tenantActor. Base-app admins acting on a child app via App Switcher\nreach this endpoint through the same path as other app-scope chat reads.","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","minimum":0,"default":0}}],"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"responses":{"200":{"description":"```\n{\n  results: [\n    {\n      _id: string,            // Mongo chat _id\n      name: string,           // bare room name, \"${appId}_<roomUuid>\"\n      title: string,\n      type: 'widget',\n      createdAt: ISODate,\n      updatedAt: ISODate,\n      visitor: {\n        _id: string,\n        uuid: string,\n        xmppUsername: string,\n        firstSeenAt: ISODate\n      } | null\n    }\n  ],\n  pagination: { limit: number, offset: number, total: number },\n  total: number, limit: number, offset: number,\n  items: [...same as results...]\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing / invalid token."},"403":{"description":"Caller has no access to this app."},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"get_v2_apps_appid_widget_conversations"}},"/v2/apps/{appId}/chats/{chatId}/messages":{"get":{"tags":["Chats"],"summary":"Read message history for a chat (any type) from the MAM archive","description":"**Auth**: Tenant Actor\n\nReturns the message history for a single chat room, fetched directly from\nejabberd's `mod_mam` archive table. Generic — works for any chat type\n(`widget`, `group`, `private`); this is the same endpoint the admin\nAI Widget Conversations modal calls for inline transcripts and which can\nbe reused for any other chat-inspection surface.\n\nThe chat must exist AND belong to the requested app — even if the caller\nknows the chatId, archives for chats in other apps are not exposed.\n\nPagination uses a `before` cursor in milliseconds-since-epoch on the\nmessage timestamp. Pass `nextBefore` from a prior response back as\n`before` to fetch the next older page. Results are returned in\nchronological order (oldest first) so the UI can render top-to-bottom\nwithout re-sorting.\n\nInstalls that haven't wired the `MAM_MYSQL_*` env vars get\n`mamUnavailable: true` and an empty `results` array — the UI should\nrender a \"history viewer not configured\" hint rather than a broken state.","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"chatId","required":true,"schema":{"type":"string"},"description":"Mongo `_id` of the chat row."},{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"in":"query","name":"before","schema":{"type":"integer","minimum":0},"description":"Cursor in ms-since-epoch (exclusive upper bound on `ts`)."}],"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"responses":{"200":{"description":"```\n{\n  results: [\n    {\n      id: string,             // ejabberd archive row id (stable, sortable)\n      originId: string|null,  // client-side message id when available\n      ts: number,             // ms-since-epoch\n      from: string,           // sender bare JID\n      nick: string,           // sender's MUC nickname\n      body: string            // plain-text message body\n    }\n  ],\n  total: number,\n  nextBefore: number|null,\n  chat: { _id: string, name: string, type: string },\n  mamUnavailable: boolean    // true when MAM_MYSQL_* env not configured\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Chat not found, or not in the requested app.\n```\n{ error: string, code: 'CHAT_NOT_FOUND' }\n```"},"422":{"description":"Validation error (missing chatId, etc.).\n```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"description":"MySQL read failed (network / auth / etc.).\n```\n{ error: string, code: 'MAM_READ_FAILED' }\n```"}},"operationId":"get_v2_apps_appid_chats_chatid_messages"},"delete":{"tags":["Chats"],"summary":"Bulk-delete the message history of a chat from the MAM archive","description":"**Auth**: Tenant Actor\n\nRemoves every `mod_mam` row attached to a single chat room. Generic —\nsame scope as the GET sibling: works for any chat type. The chat row\nitself is **not** deleted (callers that want the conversation gone\nentirely also call `DELETE /v2/apps/{appId}/chats` with the chat's\n`name`). Used by the admin AI Widget Conversations cleanup flow but\ngenerally useful for any \"clear transcript\" operation.\n\nIdempotent — calling on an already-empty chat returns\n`{ deletedCount: 0 }`.\n\nInstalls without the `MAM_MYSQL_*` env vars get `mamUnavailable: true`\nand a no-op response so the UI can render a \"history not deletable on\nthis deployment\" hint rather than fail.","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"chatId","required":true,"schema":{"type":"string"},"description":"Mongo `_id` of the chat row."}],"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"responses":{"200":{"description":"```\n{\n  deletedCount: number,\n  chat: { _id: string, name: string, type: string },\n  mamUnavailable: boolean\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Chat not found, or not in the requested app.\n```\n{ error: string, code: 'CHAT_NOT_FOUND' }\n```"},"422":{"description":"Validation error (missing chatId, etc.).\n```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"description":"MySQL delete failed (network / auth / etc.).\n```\n{ error: string, code: 'MAM_DELETE_FAILED' }\n```"}},"operationId":"delete_v2_apps_appid_chats_chatid_messages"}},"/v2/apps/{appId}/messages/search":{"get":{"tags":["Chats"],"summary":"Search archived chat messages by text content","description":"Searches the Mongo archive (`*_chat_archive.messages`) populated by the\nejabberd `mod_track_message` hook. This is a parallel store to the live\nMAM history — denormalized, indexed for `$text` + substring queries, and\nisolated from the live XMPP DB so search load never touches the chat\nruntime.\n\nTwo query modes:\n- `substring` (default): case-insensitive regex on `body`. Finds short\n  queries like `\"h\"` inside `\"hello\"` — good for autocomplete-style\n  flows. Special regex characters are escaped, so a query like `\".\"`\n  matches a literal dot, not \"any char\".\n- `fulltext`: Mongo `$text` operator. Token-based; won't match\n  substrings of a word, but very fast and supports `sort=relevance`\n  with a `score` field on each item.\n\nVisibility:\n- **B2B server token**: full access to every message in the target app.\n- **User token**: restricted to chats the caller can see. Groupchat\n  membership is resolved via `user_to_chats`; private 1-1 access is\n  derived from the canonical chatId (the caller's xmppUsername must be\n  one of the two participants).\n\nWhen the caller passes an explicit `chatId` they have no access to,\nthe response is `403 CHAT_FORBIDDEN` (not an empty list) so the\nclient can distinguish \"denied\" from \"no matches\".\n\nNote: the archive only stores messages whose chat / users are\nregistered in the Ethora API. Ad-hoc XMPP rooms and synthetic test\nfixtures don't pollute the searchable set.","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"query","name":"q","required":true,"schema":{"type":"string","minLength":1,"maxLength":500},"description":"Search query (1..500 chars). Always required."},{"in":"query","name":"mode","description":"Search mode: `text` (default; substring match on message body) or\n`semantic` (vector search via the AI service, if enabled).\n","schema":{"type":"string","enum":["substring","fulltext"],"default":"substring"}},{"in":"query","name":"chatId","schema":{"type":"string"},"description":"Optional chat localpart (`${appId}_${uuid}`, no `@conference.host`).\nWhen provided, scopes search to one chat. For user tokens, returns\n403 CHAT_FORBIDDEN if the caller has no access to it."},{"in":"query","name":"fromUserId","schema":{"type":"string"},"description":"Bare uuid (not xmppUsername) — filters by sender."},{"in":"query","name":"since","description":"Lower bound (inclusive) on `message.createdAt`. ISO-8601\ntimestamp or Unix-epoch seconds.\n","schema":{"type":"string","format":"date-time"}},{"in":"query","name":"until","description":"Upper bound (exclusive) on `message.createdAt`. Same format as\n`since`.\n","schema":{"type":"string","format":"date-time"}},{"in":"query","name":"sort","schema":{"type":"string","enum":["date","relevance"],"default":"date"},"description":"`relevance` is only meaningful with `mode=fulltext`; with substring\nit falls back to `date desc`."},{"in":"query","name":"limit","description":"Maximum number of items to return per page. Server caps vary per\nendpoint; the response carries the actual page size.\n","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"in":"query","name":"offset","description":"Number of items to skip from the beginning of the result set. Use\nwith `limit` for paging.\n","schema":{"type":"integer","minimum":0,"default":0}}],"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"responses":{"200":{"description":"```\n{\n  items: [\n    {\n      _id, appId, chatId, chatType, room,\n      from, fromUserId, to, toUserId,\n      body, messageId, stanzaId, createdAt,\n      score?    // only when mode=fulltext AND sort=relevance\n    }\n  ],\n  total: number,\n  limit, offset, q, mode\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Caller has no access to the requested chat (only when `chatId` is provided).\n```\n{ error: string, code: 'CHAT_FORBIDDEN', details: { chatId } }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Validation error (missing `q`, `q` too long, invalid mode/sort, limit > 100, etc.).\n```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"get_v2_apps_appid_messages_search"}},"/v2/apps/{appId}/chats/{chatId}/messages/context":{"get":{"tags":["Chats"],"summary":"Load message context around a target (for \"jump to message\" UX)","description":"**Auth**: Tenant Actor\n\nReturns the target message plus `radius` messages on each side, all\nfrom the same Mongo archive as `/messages/search`. Built for the\n\"user clicks a search hit → land in the right spot of the chat\" flow:\none request gives the surrounding window of history so the frontend\ncan render it and scroll the target into view.\n\nReads come from the archive (not MAM) so that whatever the search\nendpoint returned is guaranteed to be present in the context window.\n\nPagination semantics use a `(createdAt, _id)` tie-break, so two\nmessages with the same millisecond timestamp don't reshuffle between\nrequests. Use `hasMoreBefore` / `hasMoreAfter` to decide whether to\nfetch the next outer window.\n\nVisibility identical to search: B2B sees everything, user tokens\nmust have access to the requested chat (groupchat membership or\nprivate-chat participant), otherwise 403.","parameters":[{"in":"path","name":"appId","description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","required":true,"schema":{"type":"string"}},{"in":"path","name":"chatId","required":true,"schema":{"type":"string"},"description":"Localpart (`${appId}_${uuid}` for groupchat, sorted-uuids for private)."},{"in":"query","name":"aroundStanzaId","schema":{"type":"string"},"description":"XEP-0359 server-assigned id (preferred — globally unique within\nthe chat). Provide this OR `aroundMessageId`."},{"in":"query","name":"aroundMessageId","schema":{"type":"string"},"description":"Client-side message id (XMPP `<message id=\"...\">`). Fallback for\nclients that don't have a stanza-id."},{"in":"query","name":"radius","schema":{"type":"integer","minimum":0,"maximum":50,"default":10},"description":"How many messages to load on each side of the target."}],"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"responses":{"200":{"description":"Items are returned in chronological order (oldest first), with\nthe target somewhere in the middle.\n```\n{\n  items: [\n    { _id, appId, chatId, chatType, room, from, fromUserId,\n      to, toUserId, body, messageId, stanzaId, createdAt }\n  ],\n  target: { ...same shape as items[N]... },\n  hasMoreBefore: boolean,\n  hasMoreAfter:  boolean,\n  appId, chatId, radius\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"User token has no access to this chat.\n```\n{ error: string, code: 'CHAT_FORBIDDEN', details: { chatId } }\n```"},"404":{"description":"No message with that stanzaId / messageId in this chat's archive.\n```\n{ error: string, code: 'TARGET_NOT_FOUND',\n  details: { chatId, aroundStanzaId, aroundMessageId } }\n```"},"422":{"description":"Validation error — missing chatId, missing both around* params,\nradius out of [0..50].\n```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"get_v2_apps_appid_chats_chatid_messages_context"}},"/v2/apps/{appId}/users/unread-counts":{"post":{"tags":["Chats"],"summary":"Batch unread message counts per room for a set of users","description":"Returns, for each requested user, how many unread messages they have\nper chat room in the target app. Built for backend / CRON flows -\ne.g. a scheduled \"you have unread messages\" digest email - where your\nbackend iterates its own users and asks Ethora for their unread state.\n\n**Why POST, not GET:** the input is an array of user ids, which would\nnot fit reliably in a query string. The call is otherwise a plain\nsynchronous read - it returns the counts immediately, nothing is\nqueued or processed in the background.\n\n**How unread is derived (read-only, no client change required):**\nper room, a cutoff is chosen as the user's `lastViewed` marker for\nthat room (persisted by the chat clients in ejabberd private storage),\nor - if the user never opened the room - the time they were added to\nit. Unread = messages in that room newer than the cutoff, excluding\nthe user's own. Messages are counted from the Mongo archive\n(`*_chat_archive.messages`, the same store as `/messages/search`), so\nthis load never touches the live XMPP database.\n\n**Modes:**\n- `count` (default): exact per-room counts, each bounded by `cap`\n  (a room with more than `cap` unread reports `cap` with\n  `capped: true`, i.e. \"99+\").\n- `flag`: skips exact counting and only reports whether each room has\n  any unread (`unreadCount` collapses to 1). Cheapest option for very\n  large / hot deployments.\n\n**Scope & limits:** all `userIds` must belong to `appId`. Identifiers\nmay be a user's uuid, Mongo `_id`, or xmppUsername. Max 200 users per\nrequest - split larger runs into multiple calls. Ids that don't\nresolve to a user in the app are returned in `notFound` rather than\nfailing the request.\n\n**Accuracy note:** this is a best-effort count for nudge/notification\nuse. It can differ slightly from the in-app badge (which zeroes the\nactive room live and is multi-device-synced only on app open). For a\nperiodic digest that difference is immaterial.","parameters":[{"in":"path","name":"appId","required":true,"description":"Target App ID. Must be an app the calling actor owns or has been\ndelegated access to.\n","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userIds"],"properties":{"userIds":{"type":"array","minItems":1,"maxItems":200,"items":{"type":"string"},"description":"User identifiers (uuid / Mongo _id / xmppUsername), all within appId."},"mode":{"type":"string","enum":["count","flag"],"default":"count","description":"`count` = exact per-room counts (capped); `flag` = presence only."},"cap":{"type":"integer","minimum":1,"maximum":1000,"default":99,"description":"Per-room count ceiling. Counts at or above this report capped=true."}}}}}},"security":[{"UserJwtAuth":[]},{"B2BServerJwtAuth":[]}],"responses":{"200":{"description":"Per-user unread breakdown. Rooms with zero unread are omitted from\neach user's `rooms` array.\n```\n{\n  appId: string,\n  mode: 'count' | 'flag',\n  cap: number,\n  markerSource: 'private-storage' | 'membership-fallback',\n  results: [\n    {\n      userId: string,        // the identifier you passed in\n      uuid: string | null,\n      xmppUsername: string | null,\n      total: number,         // sum of per-room unreadCount\n      hasUnread: boolean,\n      unreadRoomCount: number,\n      capped: boolean,       // any room hit the cap\n      rooms: [\n        { chatId: string, unreadCount: number, capped: boolean }\n      ]\n    }\n  ],\n  notFound: string[]         // ids that didn't resolve to a user\n}\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Caller has no access to the target app.\n```\n{ error: string, code: 'FORBIDDEN' }\n```"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Validation error (empty `userIds`, more than 200 ids, invalid mode/cap).\n```\n{ error: string, code: 'VALIDATION_ERROR' }\n```"},"500":{"$ref":"#/components/responses/InternalServerError"}},"operationId":"post_v2_apps_appid_users_unread_counts"}},"/v2/apps/{appId}/ai-usage":{"get":{"summary":"AI tokens an App spent (v2, tenant actor)","description":"Model tokens charged to the App from the daily ledger: agent replies, the\nsmart gate, heartbeat turns, flow answers, the agent reply API, and the\nembeddings for Web Index pages, uploaded documents and knowledge search.\n`from`/`to` are UTC days, `to` exclusive; the default is the current month\nto date. `monthToDate` is always included.\n","tags":["Apps"],"operationId":"v2_apps_ai_usage_get","parameters":[{"in":"path","name":"appId","required":true,"schema":{"type":"string"}},{"in":"query","name":"from","required":false,"schema":{"type":"string","format":"date"}},{"in":"query","name":"to","required":false,"schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"```\n{ ok: true, result: { appId, from, to, monthToDate,\n  total: { calls, inputTokens, outputTokens, totalTokens, estimatedTokens, chatTokens, embeddingTokens },\n  byKind: [{ kind, ... }], byAgent: [{ agentId, ... }], byModel: [{ model, ... }], byDay: [{ date, ... }] } }\n```"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"validation error"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/v2/agents/{idOrAddress}/ai-usage":{"get":{"summary":"AI tokens an agent used this month (owner only)","description":"Tokens charged to this agent this UTC month, across every App it answers in,\nby kind (replies, reply checks, scheduled messages, flow answers, the agent\nreply API, Web Index / document indexing, knowledge search), and last month's\ntotal for comparison.\n","tags":["Agents"],"operationId":"v2_agents_ai_usage_get","parameters":[{"in":"path","name":"idOrAddress","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"`{ ok: true, result: { agentId, month, monthToDate, byKind: [{ kind, totalTokens, calls }], lastMonth } }`"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"not the agent's owner"},"404":{"description":"no such agent"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}}}