API Reference
Connect RPC API — 10 services, 79 endpoints
https://api.nexus-dev.xsyphon.comProtocol: Connect RPCAgentService
12 endpointsAgentService handles agent discovery, Mini App launch, and developer management of agents.
Discovery (any authenticated identity):
- Users browse featured agents via ListFeaturedAgents.
- Users view agent public profile via GetAgentInfo.
Mini App launch (user_only):
- Users obtain signed initData to launch a Mini App via GetMiniAppLaunchData.
Developer management (user_only):
- Developers create agents via CreateAgent.
- Developers list their agents via ListMyAgents.
- Developers view full agent profile via GetMyAgent.
- Developers update agent config via SetAgentConfig.
- Developers delete agents via DeleteMyAgent.
- Developers regenerate agent token via RegenerateAgentToken.
- Developers regenerate agent secret key via RegenerateAgentSecretKey.
- Developers configure Mini App via SetAgentMiniApp.
Agent self-management:
- Agents update their own config via UpdateAgentSelfConfig.
Relationship to other services:
- Adding an agent creates a conversation (type = AGENT) visible in ConversationService.ListConversations.
- Adding an agent to a group is handled by GroupService.AddAgent.
ListFeaturedAgents
ListFeaturedAgents returns a batch of recommended agents for the discovery page. No pagination; call again to get a different batch.
Request Body
api.v1.ListFeaturedAgentsRequestlimitrequiredMaximum results to return (default: 20, max: 50).
/api.v1.AgentService/ListFeaturedAgentscurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AgentService/ListFeaturedAgents \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"limit": 0
}'GetAgentInfo
GetAgentInfo returns public profile info for a specific agent.
Error conditions:
- NOT_FOUND: Agent does not exist or has been deleted.
/api.v1.AgentService/GetAgentInfocurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AgentService/GetAgentInfo \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"agentUserId": 0
}'GetMiniAppLaunchData
User OnlyGetMiniAppLaunchData generates signed initData for launching a Mini App.
Error conditions:
- FAILED_PRECONDITION: Agent has not enabled Mini App.
- PERMISSION_DENIED: User has no access to this agent or conversation_id mismatch.
Request Body
api.v1.GetMiniAppLaunchDataRequestagentUserIdrequiredTarget agent user ID.
conversationIdrequiredConversation ID (private chat or group chat). Zero means no conversation context.
startParamrequiredStart parameter (from Direct Link or Card Action).
platformrequiredClient platform ("ios" / "android" / "desktop").
Response
api.v1.GetMiniAppLaunchDataResponseinitDatarequiredSigned initData query string.
miniAppUrlrequiredMini App entry URL.
/api.v1.AgentService/GetMiniAppLaunchDatacurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AgentService/GetMiniAppLaunchData \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"agentUserId": 0,
"conversationId": 0,
"startParam": "string",
"platform": "string"
}'CreateAgent
User OnlyCreateAgent creates a new agent on behalf of the authenticated user.
Side effects:
- Establishes bidirectional contact relationship between developer and agent.
- Auto-generates Agent Token and secret_key.
Error conditions:
- ALREADY_EXISTS: Username is taken.
- INVALID_ARGUMENT: Invalid username or name.
Request Body
api.v1.CreateAgentRequestusernamerequiredAgent unique username.
namerequiredAgent display name.
signaturerequiredAgent signature / description.
Response
api.v1.CreateAgentResponse/api.v1.AgentService/CreateAgentcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AgentService/CreateAgent \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"username": "aaaaa",
"name": "a",
"signature": "string"
}'ListMyAgents
User OnlyListMyAgents lists all agents created by the authenticated user.
/api.v1.AgentService/ListMyAgentscurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AgentService/ListMyAgents \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{}'GetMyAgent
User OnlyGetMyAgent returns the full profile of an agent owned by the authenticated user.
Error conditions:
- NOT_FOUND: Agent does not exist.
- PERMISSION_DENIED: Caller is not the agent creator.
Response
api.v1.GetMyAgentResponse/api.v1.AgentService/GetMyAgentcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AgentService/GetMyAgent \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"agentUserId": 0
}'SetAgentConfig
User OnlySetAgentConfig updates configuration fields of an agent. Only provided fields are updated; omitted fields remain unchanged. When delivery_mode is set to WEBHOOK, webhook_url must also be provided.
Error conditions:
- NOT_FOUND: Agent does not exist.
- PERMISSION_DENIED: Caller is not the agent creator.
- INVALID_ARGUMENT: Invalid field values or webhook URL.
Request Body
api.v1.SetAgentConfigRequestagentUserIdrequiredTarget agent user ID.
ipWhitelistrequired arrayIP whitelist (replaces existing). Pass empty list to clear.
webhookUrlWebhook URL (required when delivery_mode is WEBHOOK, must be HTTPS).
/api.v1.AgentService/SetAgentConfigcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AgentService/SetAgentConfig \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"agentUserId": 0,
"ipWhitelist": [
"string"
]
}'DeleteMyAgent
User OnlyDeleteMyAgent permanently deletes an agent owned by the authenticated user.
Side effects:
- Sets agent status to DELETED.
- Removes the agent from all group memberships.
- Existing conversations become read-only.
Error conditions:
- NOT_FOUND: Agent does not exist.
- PERMISSION_DENIED: Caller is not the agent creator.
/api.v1.AgentService/DeleteMyAgentcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AgentService/DeleteMyAgent \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"agentUserId": 0
}'RegenerateAgentToken
User OnlyRegenerateAgentToken regenerates the API token for an agent. The old token becomes invalid immediately.
Error conditions:
- NOT_FOUND: Agent does not exist.
- PERMISSION_DENIED: Caller is not the agent creator.
Response
api.v1.RegenerateAgentTokenResponsetokenrequiredsensitiveNew API token (nxa_xxx format).
/api.v1.AgentService/RegenerateAgentTokencurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AgentService/RegenerateAgentToken \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"agentUserId": 0
}'RegenerateAgentSecretKey
User OnlyRegenerateAgentSecretKey regenerates the HMAC secret key for an agent. The old key becomes invalid immediately.
Error conditions:
- NOT_FOUND: Agent does not exist.
- PERMISSION_DENIED: Caller is not the agent creator.
Response
api.v1.RegenerateAgentSecretKeyResponsesecretKeyrequiredsensitiveNew HMAC secret key (plaintext).
/api.v1.AgentService/RegenerateAgentSecretKeycurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AgentService/RegenerateAgentSecretKey \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"agentUserId": 0
}'SetAgentMiniApp
User OnlySetAgentMiniApp configures the Mini App for an agent.
Error conditions:
- NOT_FOUND: Agent does not exist.
- PERMISSION_DENIED: Caller is not the agent creator.
- INVALID_ARGUMENT: URL is not a valid HTTPS URL.
Request Body
api.v1.SetAgentMiniAppRequestagentUserIdrequiredTarget agent user ID.
enabledrequiredWhether to enable Mini App.
urlrequiredMini App entry URL (must be HTTPS).
allowedOriginsrequired arrayAllowed web origins for security validation.
permissionsrequiredPermission bitmask.
/api.v1.AgentService/SetAgentMiniAppcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AgentService/SetAgentMiniApp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"agentUserId": 0,
"enabled": false,
"url": "string",
"allowedOrigins": [
"string"
],
"permissions": 0
}'UpdateAgentSelfConfig
Agent OnlyUpdateAgentSelfConfig allows an authenticated agent to update its own configuration. The agent is identified by the caller's Bearer token; no agent_user_id field is required. Only agents may call this RPC.
Error conditions:
- PERMISSION_DENIED: Caller is not an agent.
- FAILED_PRECONDITION: Agent is not active.
- INVALID_ARGUMENT: Invalid field values or webhook URL.
Request Body
api.v1.UpdateAgentSelfConfigRequest/api.v1.AgentService/UpdateAgentSelfConfigcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AgentService/UpdateAgentSelfConfig \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{}'UserService
7 endpointsUserService handles user profile, device sessions, and username resolution. Authenticated via Access Token (User or Agent).
GetProfile
GetProfile returns the current user profile.
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
/api.v1.UserService/GetProfilecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.UserService/GetProfile \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{}'UpdateProfile
UpdateProfile updates the current user profile fields.
Side effects:
- If avatar_url changes, the old avatar is not deleted (clients may still cache it).
- Delivers a UserProfileUpdatedEvent as an SnUpdate to the user's own update stream only (for multi-device sync). Contacts refresh cached profile data on demand.
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
- INVALID_ARGUMENT: Nickname exceeds 64 characters or signature exceeds 200 characters.
Request Body
api.v1.UpdateProfileRequestnicknameNew nickname (optional).
signatureNew signature (optional).
avatarUrlNew avatar URL (optional).
/api.v1.UserService/UpdateProfilecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.UserService/UpdateProfile \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{}'ListDevices
User OnlyListDevices returns all active device sessions.
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
/api.v1.UserService/ListDevicescurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.UserService/ListDevices \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{}'RemoveDevice
User OnlyRemoveDevice removes a specific device session.
Side effects:
- Revokes the access and refresh tokens for the target device.
- Closes the long connection for the target device if active.
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
- NOT_FOUND: Device does not exist or does not belong to the user.
- INVALID_ARGUMENT: Cannot remove the current device (use Logout).
/api.v1.UserService/RemoveDevicecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.UserService/RemoveDevice \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"deviceId": "a"
}'SetUsername
User OnlySetUsername sets the username for the current user (first-time only).
Side effects:
- Sets the username globally (unique constraint enforced).
- The username becomes resolvable via ResolveUsername.
- Delivers a UsernameChangedEvent as an SnUpdate to the user's own update stream only (multi-device sync).
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
- FAILED_PRECONDITION: User already has a custom username set.
- ALREADY_EXISTS: Username is already taken.
- INVALID_ARGUMENT: Username does not match the required format (3-32 chars, lowercase alphanumeric and underscores only).
Request Body
api.v1.SetUsernameRequestusernamerequiredDesired username (must be globally unique, 5-32 chars, lowercase alphanumeric and underscores only).
/api.v1.UserService/SetUsernamecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.UserService/SetUsername \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"username": "aaaaa"
}'ResolveUsername
ResolveUsername resolves a username to public user info.
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
- NOT_FOUND: No user exists with this username.
Request Body
api.v1.ResolveUsernameRequestusernamerequiredUsername to resolve (without @ prefix).
Response
api.v1.ResolveUsernameResponse/api.v1.UserService/ResolveUsernamecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.UserService/ResolveUsername \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"username": "aaaaa"
}'BatchGetUserInfo
BatchGetUserInfo returns public user info for a batch of user IDs. Used by clients to populate user caches (e.g., conversation list, group member list, message sender info).
IDs that do not exist or belong to deleted accounts are silently omitted from the response (no error raised).
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
- INVALID_ARGUMENT: user_ids is empty or exceeds 200 items.
Request Body
api.v1.BatchGetUserInfoRequestuserIdsrequired arrayUser IDs to look up. Max 200 items per request.
Response
api.v1.BatchGetUserInfoResponse/api.v1.UserService/BatchGetUserInfocurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.UserService/BatchGetUserInfo \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"userIds": [
0
]
}'AuthService
12 endpointsAuthService handles authentication, token management, and password operations. Authenticated via Access Token (except where skip_auth is set).
RequestVerifyCode
User OnlyNo AuthRequestVerifyCode sends a verification code to the given identity.
Side effects:
- Generates a time-limited verification code and sends it via the appropriate channel (SMS for phone, email for email).
- Creates a verification session referenced by verify_token.
- Rate-limited: max 1 code per identity per 60 seconds.
Error conditions:
- INVALID_ARGUMENT: Identity type or value is invalid.
- RESOURCE_EXHAUSTED: Rate limit exceeded for this identity.
Request Body
api.v1.RequestVerifyCodeRequestResponse
api.v1.RequestVerifyCodeResponseverifyTokenrequiredsensitiveOpaque token to reference this verification session.
expiresInrequiredToken expiration in seconds.
/api.v1.AuthService/RequestVerifyCodecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AuthService/RequestVerifyCode \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"identityType": 0,
"identityValue": "a"
}'VerifyCode
User OnlyNo AuthVerifyCode validates the code and returns auth tokens.
Side effects:
- If the identity is new, creates a user account (is_new_user=true).
- Creates a device session and issues access + refresh tokens.
- Invalidates the verification session (single-use).
Error conditions:
- INVALID_ARGUMENT: Code is incorrect or verify_token is malformed.
- NOT_FOUND: Verification session does not exist or has expired.
- RESOURCE_EXHAUSTED: Too many failed attempts (max 5 per session).
Request Body
api.v1.VerifyCodeRequestResponse
api.v1.VerifyCodeResponseuserIdrequiredAuthenticated user ID.
deviceIdrequiredDevice ID for this session.
isNewUserrequiredWhether this is a newly registered user.
accessTokenrequiredsensitiveAccess token.
refreshTokenrequiredsensitiveRefresh token for token renewal.
expiresInrequiredAccess token expiration in seconds.
/api.v1.AuthService/VerifyCodecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AuthService/VerifyCode \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"verifyToken": "a",
"code": "a",
"deviceInfo": {}
}'LoginPassword
User OnlyNo AuthLoginPassword authenticates with password credentials.
Side effects:
- Creates a device session and issues access + refresh tokens.
Error conditions:
- INVALID_ARGUMENT: Identity type or value is invalid.
- NOT_FOUND: No account exists for this identity.
- UNAUTHENTICATED: Password is incorrect.
- RESOURCE_EXHAUSTED: Too many failed login attempts (account temporarily locked after 10 consecutive failures).
Request Body
api.v1.LoginPasswordRequestResponse
api.v1.LoginPasswordResponse/api.v1.AuthService/LoginPasswordcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AuthService/LoginPassword \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"identityType": 0,
"identityValue": "a",
"password": "aaaaaaaa",
"deviceInfo": {}
}'RefreshToken
User OnlyNo AuthRefreshToken exchanges a refresh token for a new access token.
Side effects:
- Issues a new access token with a fresh expiration.
Error conditions:
- UNAUTHENTICATED: Refresh token is invalid, expired, or revoked.
Request Body
api.v1.RefreshTokenRequestrefreshTokenrequiredsensitiveCurrent refresh token.
Response
api.v1.RefreshTokenResponseaccessTokenrequiredsensitiveNew access token.
expiresInrequiredAccess token expiration in seconds.
refreshTokenrequiredsensitiveRotated refresh token. The previous refresh token is invalidated.
/api.v1.AuthService/RefreshTokencurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AuthService/RefreshToken \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"refreshToken": "a"
}'Logout
User OnlyLogout signs out the current device session.
Side effects:
- Revokes the access and refresh tokens for the current device.
- Closes the long connection for this device if active.
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
/api.v1.AuthService/Logoutcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AuthService/Logout \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{}'LogoutAll
User OnlyLogoutAll signs out all device sessions for the user.
Side effects:
- Revokes all access and refresh tokens across all devices.
- Closes all active long connections for the user.
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
/api.v1.AuthService/LogoutAllcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AuthService/LogoutAll \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{}'SetupPassword
User OnlySetupPassword sets a password for a passwordless account.
Side effects:
- Stores the password securely for the user account.
- Enables password-based login for this account.
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
- FAILED_PRECONDITION: Account already has a password set.
- INVALID_ARGUMENT: Password does not meet complexity requirements.
Request Body
api.v1.SetupPasswordRequestnewPasswordrequiredsensitiveNew password to set.
/api.v1.AuthService/SetupPasswordcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AuthService/SetupPassword \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"newPassword": "aaaaaaaa"
}'ChangePassword
User OnlyChangePassword changes the current password.
Side effects:
- Updates the stored password.
- Revokes all other device sessions (forces re-login).
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
- INVALID_ARGUMENT: New password does not meet complexity requirements.
- UNAUTHENTICATED: Old password is incorrect.
- FAILED_PRECONDITION: Account does not have a password set.
Request Body
api.v1.ChangePasswordRequestoldPasswordrequiredsensitiveCurrent password for verification.
newPasswordrequiredsensitiveNew password to set.
/api.v1.AuthService/ChangePasswordcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AuthService/ChangePassword \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"oldPassword": "aaaaaaaa",
"newPassword": "aaaaaaaa"
}'ResetPasswordRequest
User OnlyNo AuthResetPasswordRequest initiates a password reset flow (step 1 of 2).
Sends a verification code to the identity and returns a verify_token. The client then calls ResetPasswordVerify to exchange the code for a one-time reset_token, and finally ResetPasswordConfirm to set the new password.
The verification code session is independent from RequestVerifyCode (login flow). They use separate sessions, so a login code cannot be used for password reset and vice versa.
Side effects:
- Generates a verification code and sends it via SMS or email.
- Creates a reset verification session referenced by verify_token.
- Rate-limited: same limits as RequestVerifyCode per identity.
Error conditions:
- INVALID_ARGUMENT: Identity type or value is invalid.
- NOT_FOUND: No account exists for this identity.
- RESOURCE_EXHAUSTED: Rate limit exceeded.
Request Body
api.v1.ResetPasswordRequestRequestResponse
api.v1.ResetPasswordRequestResponseverifyTokenrequiredsensitiveOpaque token to reference this reset session.
expiresInrequiredToken expiration in seconds.
/api.v1.AuthService/ResetPasswordRequestcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AuthService/ResetPasswordRequest \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"identityType": 0,
"identityValue": "a"
}'ResetPasswordVerify
User OnlyNo AuthResetPasswordVerify validates the reset verification code (step 2 of 2a).
On success, returns a one-time reset_token (TTL 10 min) that the client uses in ResetPasswordConfirm.
Side effects:
- Invalidates the verification session (single-use).
- Issues a one-time reset_token (TTL 10 min).
Error conditions:
- INVALID_ARGUMENT: Code is incorrect.
- NOT_FOUND: Verification session does not exist or has expired.
- RESOURCE_EXHAUSTED: Too many failed attempts (max 5 per session).
Request Body
api.v1.ResetPasswordVerifyRequestverifyTokenrequiredReset verification session token (from ResetPasswordRequestResponse).
coderequiredUser-entered verification code.
Response
api.v1.ResetPasswordVerifyResponseresetTokenrequiredsensitiveOne-time reset token (TTL 10 min). Use in ResetPasswordConfirm.
expiresInrequiredReset token expiration in seconds.
/api.v1.AuthService/ResetPasswordVerifycurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AuthService/ResetPasswordVerify \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"verifyToken": "a",
"code": "a"
}'ResetPasswordConfirm
User OnlyNo AuthResetPasswordConfirm sets the new password using a reset_token (step 2 of 2b).
Side effects:
- Updates the stored password.
- Revokes all existing device sessions (forces re-login).
- Invalidates the reset_token (single-use).
Error conditions:
- INVALID_ARGUMENT: New password does not meet complexity requirements.
- NOT_FOUND: reset_token does not exist, has expired, or was already used.
Request Body
api.v1.ResetPasswordConfirmRequestresetTokenrequiredsensitiveOne-time reset token (returned by ResetPasswordRequest after successful verification).
newPasswordrequiredsensitiveNew password to set.
/api.v1.AuthService/ResetPasswordConfirmcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AuthService/ResetPasswordConfirm \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"resetToken": "a",
"newPassword": "aaaaaaaa"
}'GetClientConfig
No AuthGetClientConfig returns client-facing configuration such as gateway endpoints. Clients call this before login to discover service addresses.
Side effects: none.
Error conditions: none (always succeeds).
Response
api.v1.GetClientConfigResponse/api.v1.AuthService/GetClientConfigcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.AuthService/GetClientConfig \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{}'ContactService
12 endpointsContactService handles friend requests, contact management, and blocklist. Authenticated via Access Token.
update stream integration: All state-changing operations in this service produce SnUpdate entries delivered to the relevant users' update stream. This ensures clients can reliably sync contact state changes via the SyncService.GetDifference mechanism.
See shared.v1 (in shared/v1/updates.proto) for the full list of contact-related event payloads.
SendFriendRequest
User OnlySendFriendRequest sends a friend request to a target user.
Side effects:
- Creates a pending friend request record.
- Delivers a FriendRequestReceivedEvent to the target user's update stream, containing the requester's info and greeting.
- If the target user is online, the update is pushed in real-time via the long connection.
Error conditions:
- ALREADY_EXISTS: A pending request already exists between the two users.
- NOT_FOUND: Target user does not exist.
- PERMISSION_DENIED: Either user has blocked the other.
- FAILED_PRECONDITION: Users are already contacts.
Request Body
api.v1.SendFriendRequestRequesttargetUserIdrequiredTarget user ID to send the friend request to.
messagerequiredOptional greeting message (max 200 chars). Included in the FriendRequestReceivedEvent delivered to the target's update stream.
/api.v1.ContactService/SendFriendRequestcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ContactService/SendFriendRequest \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"targetUserId": 0,
"message": "string"
}'ListPendingRequests
ListPendingRequests lists pending incoming friend requests for the current user, ordered by creation time descending.
Request Body
api.v1.ListPendingRequestsRequestbeforeTimePagination anchor: only return requests created before this timestamp (Unix ms). Omit for the first page.
limitrequiredMaximum number of items to return (default: 20, max: 100).
/api.v1.ContactService/ListPendingRequestscurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ContactService/ListPendingRequests \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"limit": 0
}'AcceptFriendRequest
User OnlyAcceptFriendRequest accepts a pending friend request.
Side effects:
- Updates the request status from PENDING to ACCEPTED.
- Creates a mutual contact relationship between both users.
- Delivers a FriendRequestAcceptedEvent to both parties' update stream, containing each other's brief info.
- A PRIVATE conversation is created if one does not already exist.
Error conditions:
- NOT_FOUND: Request does not exist.
- FAILED_PRECONDITION: Request has already been handled (ACCEPTED/REJECTED).
/api.v1.ContactService/AcceptFriendRequestcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ContactService/AcceptFriendRequest \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"requestId": 0
}'RejectFriendRequest
User OnlyRejectFriendRequest rejects a pending friend request.
Side effects:
- Updates the request status from PENDING to REJECTED.
- Delivers a FriendRequestRejectedEvent to the requester's update stream.
Error conditions:
- NOT_FOUND: Request does not exist.
- FAILED_PRECONDITION: Request has already been handled (ACCEPTED/REJECTED).
/api.v1.ContactService/RejectFriendRequestcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ContactService/RejectFriendRequest \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"requestId": 0
}'AddContact
User OnlyAddContact adds a contact without friend request approval. Currently supports adding public agents and private agents (creator only).
Side effects:
- Creates bidirectional contact relationships.
- Delivers a ContactAddedEvent SnUpdate to the caller's update stream (multi-device sync). Clients use this to create the local contact record and the PRIVATE conversation.
- Delivers a contact.added webhook event to the agent.
Error conditions:
- NOT_FOUND: Target user does not exist.
- ALREADY_EXISTS: Contact relationship already exists.
- PERMISSION_DENIED: Private agent, caller is not the creator.
- FAILED_PRECONDITION: Target is a regular user (use SendFriendRequest).
- FAILED_PRECONDITION: Agent status is not ACTIVE (error_code 7003).
/api.v1.ContactService/AddContactcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ContactService/AddContact \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"targetUserId": 0
}'ListContacts
ListContacts returns the current user's contact list.
Request Body
api.v1.ListContactsRequestafterIdPagination anchor: only return contacts with contact_user_id > after_id. Omit for the first page. Contacts are ordered by contact_user_id ascending.
limitrequiredMaximum number of items to return (default: 50, max: 200).
Response
api.v1.ListContactsResponse/api.v1.ContactService/ListContactscurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ContactService/ListContacts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"limit": 0
}'DeleteContact
DeleteContact removes a contact from the current user's contact list. This is a single-sided operation: only the caller's contact record is removed. The other party's contact list is not affected.
Side effects:
- Delivers a ContactDeletedEvent to the caller's own update stream (multi-device sync). The other party is not notified.
- The private conversation is NOT deleted; it remains accessible but new messages cannot be sent until re-friended.
Error conditions:
- NOT_FOUND: Contact relationship does not exist.
Request Body
api.v1.DeleteContactRequestcontactUserIdrequiredUser ID of the contact to remove.
/api.v1.ContactService/DeleteContactcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ContactService/DeleteContact \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"contactUserId": 0
}'UpdateContactAlias
UpdateContactAlias sets or clears a custom alias for a contact. The alias overrides the contact's nickname in the current user's UI.
Side effects:
- Delivers a ContactAliasUpdatedEvent to the caller's own update box (multi-device sync).
Request Body
api.v1.UpdateContactAliasRequestcontactUserIdrequiredUser ID of the contact.
aliasNew alias. Set to empty or omit to clear the alias.
/api.v1.ContactService/UpdateContactAliascurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ContactService/UpdateContactAlias \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"contactUserId": 0
}'SearchUsers
SearchUsers searches for users and/or agents across the platform. Supports filtering by account type (USER, AGENT, or ALL).
For human users: exact match on phone, email, or username (returns at most 1 result). For agents: fuzzy match on nickname, username, and description (returns at most 10 results). Only PUBLIC + ACTIVE agents are returned.
Results exclude the current user and blocked users. No pagination — results are capped by design.
Request Body
api.v1.SearchUsersRequestResponse
api.v1.SearchUsersResponse/api.v1.ContactService/SearchUserscurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ContactService/SearchUsers \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"query": "a",
"accountType": 0
}'BlockUser
BlockUser blocks a user.
Side effects:
- If the blocked user is a contact, the contact relationship is removed (equivalent to DeleteContact + block).
- Delivers a UserBlockToggledEvent (is_blocked=true) to the blocker's own update stream (for multi-device sync). The blocked user is NOT notified.
- Any pending friend requests between the two users are cancelled.
- The blocked user can no longer send messages or friend requests to the blocker. The blocker also cannot send messages to the blocked user.
Error conditions:
- ALREADY_EXISTS: User is already blocked.
- NOT_FOUND: Target user does not exist.
/api.v1.ContactService/BlockUsercurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ContactService/BlockUser \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"targetUserId": 0
}'UnblockUser
UnblockUser unblocks a previously blocked user.
Side effects:
- Delivers a UserBlockToggledEvent (is_blocked=false) to the unblocker's own update stream (for multi-device sync). The unblocked user is NOT notified.
- Unblocking does NOT restore the contact relationship; the user must send a new friend request.
Error conditions:
- NOT_FOUND: User is not in the blocklist.
/api.v1.ContactService/UnblockUsercurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ContactService/UnblockUser \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"targetUserId": 0
}'ListBlocked
ListBlocked lists all blocked users for the current user.
Response
api.v1.ListBlockedResponse/api.v1.ContactService/ListBlockedcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ContactService/ListBlocked \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{}'ConversationService
4 endpointsConversationService handles conversation list retrieval, actions, and read-state management. Authenticated via Access Token.
Conversation lifecycle:
- PRIVATE conversations are created implicitly when a friend request is accepted (via ContactService.AcceptFriendRequest), or when a user adds an agent as a contact (via ContactService.AddContact).
- GROUP conversations are created via GroupService.CreateGroup.
Conversation list model:
- Conversations are paginated by the `last_message_time` field descending.
- Responses include related_users to avoid extra round-trips for rendering the conversation list UI.
GetConversation
GetConversation returns a single conversation detail by ID.
Clients should call this when they receive a message push for a conversation_id that is not in their local list (e.g., a previously deleted conversation that was auto-restored by the server).
/api.v1.ConversationService/GetConversationcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ConversationService/GetConversation \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0
}'ListConversations
ListConversations returns conversations ordered by last_message_time descending, with cursor-based pagination.
This is also the entry point for reconnection sync: after authentication, clients pull the full conversation list, compare each conversation's last_message_id with their local value, and call MessageService.GetMessageHistory for conversations with a gap.
Note: Deleted conversations that have been auto-restored (due to a new incoming message) will appear in the results normally.
Request Body
api.v1.ListConversationsRequestbeforeTimePagination anchor: only return conversations with last_message_time < before_time. Omit for the first page.
limitrequiredMaximum number of items to return (default: 20, max: 100).
Response
api.v1.ListConversationsResponsehasMorerequiredWhether there are more conversations beyond this page.
/api.v1.ConversationService/ListConversationscurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ConversationService/ListConversations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"limit": 0
}'UpdateConversationAction
UpdateConversationAction performs a unified conversation action.
Supported actions:
- MUTE: Mute conversation notifications.
- UNMUTE: Restore conversation notifications.
- DELETE: Soft-delete the conversation from the user's list.
Access control:
- Permission is checked against the underlying relationship (contact for PRIVATE, group membership for GROUP).
- If no per-user conversation state exists yet, it is created automatically.
DELETE behavior:
- Sets is_deleted flag for the current user only (per-user soft-delete).
- If clear_messages is true, also clears the user's local message history for this conversation (server-side per-user clear).
- The conversation is automatically restored (is_deleted cleared) when a new message arrives in the conversation. There is no explicit RESTORE action; restoration is implicit and server-driven.
- Online clients: receive the new message via long-connection push and should re-add the conversation to their list.
- Offline clients: the next ListConversations call naturally returns the restored conversation since is_deleted has been cleared.
- If a client receives a message for an unknown conversation_id, it should call GetConversation to fetch the full conversation info.
Side effects:
- Delivers a ConversationActionEvent to the current user's update stream for multi-device sync. Other devices consume this event to keep conversation list state consistent.
Request Body
api.v1.UpdateConversationActionRequest/api.v1.ConversationService/UpdateConversationActioncurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ConversationService/UpdateConversationAction \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0,
"action": 0,
"clearMessages": false
}'MarkAsRead
MarkAsRead updates the current user's read position in a conversation.
Access control:
- Same as UpdateConversationAction: checks underlying relationship, auto-creates per-user conversation state if needed.
Side effects:
- Updates last_read_message_id in the conversation membership.
- Produces a ReadReceiptEvent SnUpdate to the caller's own update stream (multi-device sync only; the peer is not notified).
- The server does NOT maintain unread_count. Clients calculate it locally: unread = last_message_id - last_read_message_id.
Request Body
api.v1.MarkAsReadRequestconversationIdrequiredConversation ID.
upToMessageIdrequiredMark all messages up to and including this message ID as read. Must be >= current last_read_message_id (cannot go backwards).
/api.v1.ConversationService/MarkAsReadcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.ConversationService/MarkAsRead \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0,
"upToMessageId": 0
}'GroupService
10 endpointsGroupService handles group lifecycle, membership, and settings. Authenticated via Access Token.
Group lifecycle:
- CreateGroup creates a GROUP conversation and adds the creator as owner.
- DissolveGroup permanently removes the group (owner only).
System message integration: All state-changing operations produce GroupContent system messages delivered to the group conversation. See shared/v1/group_event.proto for the full list of event payloads:
- Member changes: MemberJoinedEvent, MemberLeftEvent, MemberRemovedEvent.
- Setting changes: GroupInfoChangedEvent.
CreateGroup
User OnlyCreateGroup creates a new group conversation.
Side effects:
- Creates a GROUP conversation and adds the creator as owner.
- Delivers a single MemberJoinedEvent containing all initial members.
Error conditions:
- INVALID_ARGUMENT: Name is empty or fewer than 2 member_ids.
- FAILED_PRECONDITION: After filtering invalid/blocked users, total members (including creator) are fewer than 3.
- RESOURCE_EXHAUSTED: Total members (including creator) exceed the group member limit (200).
Request Body
api.v1.CreateGroupRequestnamerequiredGroup display name.
avatarUrlrequiredGroup avatar URL. Empty string uses system default avatar.
memberIdsrequired arrayInitial member user IDs to invite (excluding the creator who is added automatically). At least 2 members required (group minimum is 3 including the creator). Max group size is 200 including the creator.
descriptionrequiredGroup description (optional).
/api.v1.GroupService/CreateGroupcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.GroupService/CreateGroup \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"name": "a",
"avatarUrl": "string",
"memberIds": [
0
],
"description": "string"
}'DissolveGroup
User OnlyDissolveGroup permanently dissolves a group.
Side effects:
- Marks the group as DISSOLVED; no further messages can be sent.
- Removes all members from the group.
- Delivers GroupDissolvedEvent SnUpdate to every member's update stream.
Error conditions:
- NOT_FOUND: Group does not exist.
- FAILED_PRECONDITION: Group is already dissolved.
- PERMISSION_DENIED: Caller is not the group owner.
/api.v1.GroupService/DissolveGroupcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.GroupService/DissolveGroup \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"groupId": 0
}'UpdateGroupName
User OnlyUpdateGroupName updates the group display name.
Side effects:
- Delivers GroupInfoChangedEvent (field="name") to the group conversation.
Error conditions:
- NOT_FOUND: Group does not exist.
- FAILED_PRECONDITION: Group is dissolved.
- PERMISSION_DENIED: Caller is not the owner.
- INVALID_ARGUMENT: Name is empty.
Request Body
api.v1.UpdateGroupNameRequestgroupIdrequiredGroup ID.
namerequiredNew group name.
/api.v1.GroupService/UpdateGroupNamecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.GroupService/UpdateGroupName \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"groupId": 0,
"name": "a"
}'UpdateGroupAvatar
User OnlyUpdateGroupAvatar updates the group avatar.
Side effects:
- Delivers GroupInfoChangedEvent (field="avatar") to the group conversation.
Error conditions:
- NOT_FOUND: Group does not exist.
- FAILED_PRECONDITION: Group is dissolved.
- PERMISSION_DENIED: Caller is not the owner.
Request Body
api.v1.UpdateGroupAvatarRequestgroupIdrequiredGroup ID.
avatarUrlrequiredNew avatar URL.
/api.v1.GroupService/UpdateGroupAvatarcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.GroupService/UpdateGroupAvatar \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"groupId": 0,
"avatarUrl": "string"
}'UpdateGroupDescription
User OnlyUpdateGroupDescription updates the group description.
Side effects:
- Delivers GroupInfoChangedEvent (field="description") to the group conversation.
Error conditions:
- NOT_FOUND: Group does not exist.
- FAILED_PRECONDITION: Group is dissolved.
- PERMISSION_DENIED: Caller is not the owner.
Request Body
api.v1.UpdateGroupDescriptionRequestgroupIdrequiredGroup ID.
descriptionrequiredNew group description.
/api.v1.GroupService/UpdateGroupDescriptioncurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.GroupService/UpdateGroupDescription \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"groupId": 0,
"description": "string"
}'InviteMembers
User OnlyInviteMembers invites one or more users or agents to join the group. The server auto-detects member type (user vs agent) by account_type.
Side effects:
- Adds the invited members to the group.
- Delivers a single MemberJoinedEvent containing all newly added members.
Error conditions:
- NOT_FOUND: Group does not exist.
- FAILED_PRECONDITION: Group is dissolved.
- PERMISSION_DENIED: Caller is not the owner.
- RESOURCE_EXHAUSTED: Adding these members would exceed the group member limit or agent limit.
Skipped silently (no error):
- User IDs that do not exist.
- Users who have blocked the inviter (or vice versa).
- Users/agents who are already group members.
- Agents that are not active.
Request Body
api.v1.InviteMembersRequestgroupIdrequiredGroup ID.
memberIdsrequired arrayUser IDs to invite (can include both users and agents). Server enforces max group size and max agent count.
/api.v1.GroupService/InviteMemberscurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.GroupService/InviteMembers \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"groupId": 0,
"memberIds": [
0
]
}'RemoveMember
User OnlyRemoveMember removes a member (user or agent) from the group (owner only).
Side effects:
- Removes the target from the group.
- Delivers MemberRemovedEvent to the group conversation.
- Delivers RemovedFromGroupEvent SnUpdate to the removed member's update stream.
Error conditions:
- NOT_FOUND: Group does not exist, or target is not a member.
- FAILED_PRECONDITION: Group is dissolved.
- PERMISSION_DENIED: Caller is not the owner.
- INVALID_ARGUMENT: Cannot remove the group owner.
Request Body
api.v1.RemoveMemberRequestgroupIdrequiredGroup ID.
targetIdrequiredTarget member user ID (can be a user or agent).
/api.v1.GroupService/RemoveMembercurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.GroupService/RemoveMember \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"groupId": 0,
"targetId": 0
}'LeaveGroup
LeaveGroup allows a member to voluntarily leave the group. The group owner cannot leave; use DissolveGroup instead.
Side effects:
- Removes the caller from the group.
- Delivers MemberLeftEvent to the group conversation.
Error conditions:
- NOT_FOUND: Group does not exist, or caller is not a member.
- FAILED_PRECONDITION: Group is dissolved.
- PERMISSION_DENIED: Caller is the group owner (must dissolve instead).
/api.v1.GroupService/LeaveGroupcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.GroupService/LeaveGroup \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"groupId": 0
}'GetGroupInfo
GetGroupInfo returns group details with all members and their user info.
Error conditions:
- NOT_FOUND: Group does not exist.
- FAILED_PRECONDITION: Group is dissolved.
- PERMISSION_DENIED: Caller is not a member of the group.
Response
api.v1.GetGroupInfoResponse/api.v1.GroupService/GetGroupInfocurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.GroupService/GetGroupInfo \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"groupId": 0
}'ListGroups
ListGroups returns the current user's joined groups with cursor-based pagination.
Request Body
api.v1.ListGroupsRequestafterIdPagination anchor: only return groups with group_id > after_id. Omit for the first page.
limitrequiredMaximum number of items to return (default: 50, max: 200).
Response
api.v1.ListGroupsResponse/api.v1.GroupService/ListGroupscurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.GroupService/ListGroups \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"limit": 0
}'MediaService
5 endpointsMediaService handles file upload and download for all media types. Authenticated via Access Token.
Upload strategies:
- Small files (avatars, thumbnails): use UploadFile for single-request upload. Returns a permanent public URL for AVATAR/GROUP_AVATAR purpose.
- Large files (message attachments): use InitUpload → UploadChunk → CompleteUpload for resumable chunked upload. Returns a file_id; clients obtain time-limited download URLs via GetDownloadURL.
Access control:
- AVATAR / GROUP_AVATAR files are publicly accessible via permanent URL. No download RPC needed.
- MESSAGE files are private. GetDownloadURL issues a time-limited signed URL.
UploadFile
UploadFile uploads a complete file in a single request. Suitable for avatars and small media (recommended < 5 MB).
Side effects:
- Stores the file and generates metadata (dimensions, checksum).
- For AVATAR/GROUP_AVATAR purpose, generates a permanent public URL in MediaFileInfo.public_url.
- For MESSAGE purpose, generates a thumbnail if applicable.
Error conditions:
- INVALID_ARGUMENT: File is empty, content_type is missing, or purpose is UNSPECIFIED.
- RESOURCE_EXHAUSTED: File exceeds the size limit for single upload (5 MB). Use chunked upload instead.
Request Body
api.v1.UploadFileRequestResponse
api.v1.UploadFileResponse/api.v1.MediaService/UploadFilecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MediaService/UploadFile \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"fileName": "a",
"contentType": "a",
"purpose": {},
"data": ""
}'InitUpload
InitUpload initializes a resumable chunked upload session. Only for MESSAGE purpose files (attachments, videos, etc.).
Side effects:
- Creates an upload session with a TTL (default 24 hours).
Error conditions:
- INVALID_ARGUMENT: File name, content_type, or size is missing.
- RESOURCE_EXHAUSTED: File size exceeds the maximum allowed (100 MB).
Request Body
api.v1.InitUploadRequestfileNamerequiredOriginal file name.
contentTyperequiredMIME content type.
sizerequiredTotal file size in bytes.
Response
api.v1.InitUploadResponsesessionIdrequiredUpload session ID.
uploadedrequiredBytes already uploaded (non-zero when resuming a previous session).
createdAtrequiredSession creation time (Unix ms).
/api.v1.MediaService/InitUploadcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MediaService/InitUpload \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"fileName": "a",
"contentType": "a",
"size": 0
}'UploadChunk
UploadChunk uploads a file chunk to an active session.
Error conditions:
- NOT_FOUND: Session does not exist or has expired.
- INVALID_ARGUMENT: Offset does not match the expected position.
- RESOURCE_EXHAUSTED: Chunk would exceed the declared file size.
Request Body
api.v1.UploadChunkRequestsessionIdrequiredUpload session ID.
chunkrequiredsensitiveChunk binary data.
offsetrequiredByte offset of this chunk.
/api.v1.MediaService/UploadChunkcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MediaService/UploadChunk \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"sessionId": "a",
"chunk": "",
"offset": 0
}'CompleteUpload
CompleteUpload finalizes the chunked upload and returns file metadata.
Side effects:
- Validates checksum integrity and generates metadata (dimensions, thumbnail).
- Deletes the upload session.
Error conditions:
- NOT_FOUND: Session does not exist or has expired.
- FAILED_PRECONDITION: Uploaded bytes do not match declared size.
/api.v1.MediaService/CompleteUploadcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MediaService/CompleteUpload \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"sessionId": "a"
}'GetDownloadURL
GetDownloadURL returns a time-limited signed download URL for a private (MESSAGE purpose) file.
Access control: file_id is not enumerable, which provides sufficient protection against unauthorized access. No conversation membership check is performed.
Error conditions:
- NOT_FOUND: File does not exist.
Response
api.v1.GetDownloadURLResponseurlrequiredSigned download URL (expires in 1 hour).
expiresAtrequiredURL expiration time (Unix ms).
/api.v1.MediaService/GetDownloadURLcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MediaService/GetDownloadURL \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"fileId": "a"
}'MessageService
12 endpointsMessageService handles message sending, editing, deletion, recall, forwarding, and history retrieval. Authenticated via Access Token.
All message operations are performed via Connect RPC (HTTP). The long connection (WebSocket) is used exclusively for server-side push delivery (new messages, status updates, etc.).
SendMessage
SendMessage sends a message to a conversation.
Side effects:
- Persists the message and assigns a server message_id.
- Pushes an Update to all online conversation participants via their long connections.
- Triggers offline push notifications for offline participants.
- Updates the conversation's last_message_time and last_message_id.
Error conditions:
- NOT_FOUND: Conversation does not exist.
- PERMISSION_DENIED: User is not a member of the conversation, or the conversation is muted for this user (group mute).
- FAILED_PRECONDITION: Conversation is a PRIVATE chat where the contact relationship has been removed.
Request Body
api.v1.SendMessageRequestResponse
api.v1.SendMessageResponsemessageIdrequiredServer-assigned message ID.
createdAtrequiredMessage creation time (Unix ms).
/api.v1.MessageService/SendMessagecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MessageService/SendMessage \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"clientMessageId": 0,
"conversationId": 0,
"body": {}
}'EditMessage
EditMessage edits a previously sent message.
Side effects:
- Updates the message content and sets updated_at timestamp.
- Pushes an Update with the edited message to all online conversation participants.
- Triggers webhook delivery for agent members.
Error conditions:
- NOT_FOUND: Message does not exist.
- PERMISSION_DENIED: User is not the message sender.
- FAILED_PRECONDITION: Message has been recalled.
- FAILED_PRECONDITION: Message was sent more than 24 hours ago.
- INVALID_ARGUMENT: new_body.type differs from the original message type (type changes are not allowed).
Request Body
api.v1.EditMessageRequest/api.v1.MessageService/EditMessagecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MessageService/EditMessage \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0,
"messageId": 0,
"newBody": {}
}'DeleteMessages
DeleteMessages deletes messages by ID list for the current user only (local delete). Other participants are not affected.
Side effects:
- Marks the specified messages as deleted for the current user.
- Deleted messages are excluded from future GetMessageHistory responses for this user.
- Delivers a MessageDeletedEvent to the caller's own update stream (multi-device sync).
Error conditions:
- NOT_FOUND: Conversation does not exist.
- PERMISSION_DENIED: User is not a member of the conversation.
Request Body
api.v1.DeleteMessagesRequestconversationIdrequiredConversation ID.
messageIdsrequired arrayMessage IDs to delete.
/api.v1.MessageService/DeleteMessagescurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MessageService/DeleteMessages \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0,
"messageIds": [
0
]
}'DeleteHistory
DeleteHistory deletes all messages up to a given message ID for the current user only (local delete).
Side effects:
- Marks all messages with message_id <= up_to_message_id as deleted for the current user.
- Delivers a MessageDeletedEvent to the caller's own update stream (multi-device sync).
Error conditions:
- NOT_FOUND: Conversation does not exist.
- PERMISSION_DENIED: User is not a member of the conversation.
Request Body
api.v1.DeleteHistoryRequestconversationIdrequiredConversation ID.
upToMessageIdrequiredDelete up to and including this message ID.
/api.v1.MessageService/DeleteHistorycurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MessageService/DeleteHistory \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0,
"upToMessageId": 0
}'RecallMessage
RecallMessage recalls a sent message (visible to all participants). The original message content is replaced with a MessageRecalledContent message in the conversation timeline.
Side effects:
- Replaces the message body with RecalledContent (body.type becomes MESSAGE_TYPE_RECALLED).
- Pushes an Update with the recalled message to all online conversation participants.
- Triggers webhook delivery for agent members.
Error conditions:
- NOT_FOUND: Message does not exist.
- PERMISSION_DENIED: User is not the message sender.
- FAILED_PRECONDITION: Message was sent more than 24 hours ago.
- FAILED_PRECONDITION: Message is already recalled.
Request Body
api.v1.RecallMessageRequestconversationIdrequiredConversation ID.
messageIdrequiredMessage ID to recall.
/api.v1.MessageService/RecallMessagecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MessageService/RecallMessage \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0,
"messageId": 0
}'GetMessage
GetMessage returns a single message by ID.
Error conditions:
- NOT_FOUND: Message does not exist or has been deleted by this user.
- PERMISSION_DENIED: User is not a member of the conversation.
Request Body
api.v1.GetMessageRequestconversationIdrequiredConversation ID.
messageIdrequiredMessage ID.
/api.v1.MessageService/GetMessagecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MessageService/GetMessage \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0,
"messageId": 0
}'GetMessageHistory
GetMessageHistory returns message history with ID-based cursor pagination. Supports both backward (older) and forward (newer) pagination directions.
Pagination:
- Backward (load older): set before_message_id. Returns messages with message_id < before_message_id, ordered descending. When has_more=true, use the smallest message_id in the response as the next before_message_id.
- Forward (incremental pull): set after_message_id. Returns messages with message_id > after_message_id, ordered ascending. When has_more=true, use the largest message_id in the response as the next after_message_id.
- Omit both for the latest messages (equivalent to backward from the conversation's last_message_id).
Error conditions:
- NOT_FOUND: Conversation does not exist.
- PERMISSION_DENIED: User is not a member of the conversation.
Request Body
api.v1.GetMessageHistoryRequestconversationIdrequiredConversation ID.
beforeMessageIdBackward cursor: return messages with message_id < this value. When has_more=true, use the smallest message_id from the response.
afterMessageIdForward cursor: return messages with message_id > this value. When has_more=true, use the largest message_id from the response.
limitrequiredMaximum number of messages to return (default: 50, max: 200).
Response
api.v1.GetMessageHistoryResponsehasMorerequiredWhether there are more messages beyond this page.
/api.v1.MessageService/GetMessageHistorycurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MessageService/GetMessageHistory \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0,
"limit": 0
}'SubmitCardAction
SubmitCardAction reports an Adaptive Card Action.Submit to the server. The server forwards the action data to the agent that sent the card message.
Side effects:
- Delivers a CardActionPayload event to the agent via webhook.
- The agent may respond via AnswerCardAction (toast/alert) and optionally update the card via EditMessage.
Error conditions:
- NOT_FOUND: Message does not exist.
- PERMISSION_DENIED: User is not a member of the conversation.
- FAILED_PRECONDITION: Message is not a CARD type message.
Request Body
api.v1.SubmitCardActionRequestconversationIdrequiredConversation ID containing the card message.
messageIdrequiredMessage ID of the card message.
actionDatarequiredAction data from Action.Submit (JSON string, ≤ 4 KB). Contains the form input values and any static data defined in the Action.Submit's data property.
verbrequiredAction verb identifier (optional). Maps to the Action.Submit's "id" or custom "verb" property, allowing agents to distinguish between multiple submit actions on the same card.
/api.v1.MessageService/SubmitCardActioncurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MessageService/SubmitCardAction \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0,
"messageId": 0,
"actionData": "a",
"verb": "string"
}'PushStreamDelta
Agent OnlyPushStreamDelta pushes an incremental delta to a streaming message.
Error conditions:
- NOT_FOUND: Streaming message does not exist.
- FAILED_PRECONDITION: Stream has already ended or errored.
Request Body
api.v1.PushStreamDeltaRequestconversationIdrequiredConversation ID containing the streaming message.
messageIdrequiredMessage ID of the streaming message (from SendMessageResponse).
seqrequiredStream sequence number (agent-side auto-increment, starting from 1). Server uses this for dedup and gap detection.
deltarequiredIncremental text fragment.
/api.v1.MessageService/PushStreamDeltacurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MessageService/PushStreamDelta \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0,
"messageId": 0,
"seq": 0,
"delta": "a"
}'EndStream
Agent OnlyEndStream finalizes a streaming message with the accumulated content.
Side effects:
- Persists the final accumulated text as the message content.
- Pushes StreamContent(phase=END) to online participants.
Error conditions:
- NOT_FOUND: Streaming message does not exist.
- FAILED_PRECONDITION: Stream has already ended or errored.
Request Body
api.v1.EndStreamRequest/api.v1.MessageService/EndStreamcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MessageService/EndStream \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0,
"messageId": 0,
"accumulatedText": "string",
"entities": [
{}
]
}'ErrorStream
Agent OnlyErrorStream terminates a streaming message with an error.
Side effects:
- Persists the error state and partial content.
- Pushes StreamContent(phase=ERROR) to online participants.
Error conditions:
- NOT_FOUND: Streaming message does not exist.
- FAILED_PRECONDITION: Stream has already ended or errored.
Request Body
api.v1.ErrorStreamRequestconversationIdrequiredConversation ID containing the streaming message.
messageIdrequiredMessage ID of the streaming message.
errorMessagerequiredError message.
/api.v1.MessageService/ErrorStreamcurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MessageService/ErrorStream \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0,
"messageId": 0,
"errorMessage": "a"
}'AnswerCardAction
Agent OnlyAnswerCardAction responds to a card action submission.
Error conditions:
- NOT_FOUND: Card action does not exist or has expired.
Request Body
api.v1.AnswerCardActionRequestconversationIdrequiredConversation ID where the card action was submitted.
messageIdrequiredCard message ID that the action belongs to.
actionIdrequiredAction ID from the webhook event (unique within conversation).
textResponse text to display.
showAlertrequiredWhether to show as alert dialog (otherwise toast).
/api.v1.MessageService/AnswerCardActioncurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.MessageService/AnswerCardAction \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"conversationId": 0,
"messageId": 0,
"actionId": "a",
"showAlert": false
}'PushService
3 endpointsPushService handles push notification token registration and removal. Authenticated via Access Token.
RegisterToken
User OnlyRegisterToken registers a push notification token for the current device.
Side effects:
- Stores the push token associated with the device and user.
- If a token already exists for this device, it is replaced.
- The device becomes eligible for push notifications.
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
- INVALID_ARGUMENT: Token is empty or platform is UNSPECIFIED.
Request Body
api.v1.RegisterTokenRequest/api.v1.PushService/RegisterTokencurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.PushService/RegisterToken \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"deviceId": "a",
"token": "a",
"platform": {},
"isSandbox": false
}'UnregisterToken
User OnlyUnregisterToken removes the push token for a device.
Side effects:
- Deletes the push token for the specified device.
- The device stops receiving push notifications.
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
- NOT_FOUND: No push token registered for this device.
/api.v1.PushService/UnregisterTokencurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.PushService/UnregisterToken \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"deviceId": "a"
}'ClearBadge
User OnlyClearBadge clears the push notification badge count for the user.
Side effects:
- iOS: Sends a silent push with badge=0 to clear the app icon badge.
- Android: No server-side action needed (client clears locally).
Error conditions:
- UNAUTHENTICATED: Invalid or expired access token.
/api.v1.PushService/ClearBadgecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.PushService/ClearBadge \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{}'SyncService
2 endpointsSyncService provides incremental state synchronization for clients. Clients maintain a local sequence number (sn) and use these RPCs to retrieve updates that occurred after their last known state. Authenticated via Access Token.
GetDifference
User OnlyGetDifference returns updates since the client's last known sn. Clients call this on reconnect or when a gap is detected in the pushed update stream.
Response
api.v1.GetDifferenceResponsesnrequiredThe new sn after applying all returned updates.
hasMorerequiredWhether more updates remain on the server. If true, the client should call GetDifference again with the returned sn.
updateTooLongrequiredWhether the gap is too large for incremental sync. If true, the client should discard local state and perform a full resync.
/api.v1.SyncService/GetDifferencecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.SyncService/GetDifference \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"sn": 0
}'GetCurrentState
User OnlyGetCurrentState returns the server's latest sn. Used during the initial sync flow so the client can establish a baseline before fetching full state via other RPCs.
/api.v1.SyncService/GetCurrentStatecurl -X POST https://api.nexus-dev.xsyphon.com/api.v1.SyncService/GetCurrentState \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{}'Schemas
Message types and enumerations used across the API
UserInfo
shared.v1.UserInfoUserInfo is the public view of a user, used across list rendering and profile pages. Combines identity and display fields into a single reusable structure.
Online status is NOT included in this structure.
PushTokenInfo
shared.v1.PushTokenInfoPushTokenInfo defines structured push token information.
AgentCommandInfo
shared.v1.AgentCommandInfoAgentCommandInfo represents a registered agent slash command.
commandrequiredCommand name (e.g. "/help").
descriptionrequiredCommand description.
AgentCommandList
shared.v1.AgentCommandListAgentCommandList is a wrapper for serializing a list of commands as a single protobuf message.
AgentInfo
shared.v1.AgentInfoAgentInfo is the public profile view of an agent, visible to all users.
isSystemAgentrequiredWhether this is a system agent (e.g. AgentRoot).
miniAppEnabledrequiredWhether Mini App is enabled for this agent.
miniAppUrlrequiredMini App entry URL (HTTPS).
miniAppPermissionsrequiredMini App permission bitmask.
createdAtrequiredAgent creation time (Unix ms).
AgentProfile
shared.v1.AgentProfileAgentProfile is the developer-facing full detail of an agent's profile.
isSystemAgentrequiredWhether this is a system agent.
tokenPrefixrequiredAgent token prefix (e.g. "nxa_xxxx") for identification.
secretKeyPrefixrequiredSecret key prefix for identification (e.g. "abcd1234...").
ipWhitelistrequired arrayAllowed IP addresses for API access.
miniAppEnabledrequiredWhether Mini App is enabled.
miniAppUrlrequiredMini App entry URL.
miniAppAllowedOriginsrequired arrayAllowed web origins for Mini App security validation.
miniAppPermissionsrequiredMini App permission bitmask.
createdAtrequiredAgent creation time (Unix ms).
updatedAtrequiredAgent last update time (Unix ms).
User
api.v1.UserUser represents the current user profile (self view, includes private fields). Online status is NOT included and not exposed to clients.
userIdrequiredUser unique ID.
usernamerequiredUsername (globally unique public identifier, used for QR code and permanent link).
phonePhone number (private).
emailEmail address (private).
nicknamerequiredDisplay nickname.
avatarUrlrequiredAvatar URL.
signaturerequiredSignature.
hasPasswordrequiredWhether a password has been set.
createdAtrequiredAccount creation time (Unix ms).
canChangeUsernamerequiredWhether the user can change their username. Server evaluates the change policy (e.g., first-time only, once per year) and returns a simple boolean so clients do not need to know the rules.
Device
api.v1.DeviceDevice represents an active device session.
deviceIdrequiredDevice unique ID (client-generated, persisted).
deviceNamerequiredDevice name.
deviceModelrequiredDevice model.
osVersionrequiredOS version.
appVersionrequiredApp version.
loginIprequiredLogin IP address.
loginAtrequiredLogin time (Unix ms).
lastActiveAtrequiredLast active time (Unix ms).
isCurrentrequiredWhether this is the current device.
DeviceInput
api.v1.DeviceInputDeviceInput is the client-reported device info submitted during authentication.
deviceIdrequiredDevice unique ID (client-generated, persisted).
deviceNamerequiredDevice name.
deviceModelrequiredDevice model.
osVersionrequiredOS version.
appVersionrequiredApp version.
LoginConfig
api.v1.LoginConfigLoginConfig describes which login methods are enabled on this server.
emailEnabledrequiredWhether email-based login (verify code + password) is enabled.
phoneEnabledrequiredWhether phone-based login (verify code + password) is enabled.
GatewayEndpoints
api.v1.GatewayEndpointsGatewayEndpoints contains the addresses clients use to connect to the gateway.
wsUrlrequiredWebSocket endpoint, e.g. "ws://host:8444/ws".
PendingRequestItem
shared.v1.PendingRequestItemPendingRequestItem represents a friend request record. Used in events (FriendRequestReceivedEvent, FriendRequestAcceptedEvent) and in ListPendingRequestsResponse. User info is provided separately via related_users (in Update push) or a sibling users field (in RPC responses).
requestIdrequiredFriend request ID (server-assigned, auto-increment).
fromUserIdrequiredUser ID of the requester.
toUserIdrequiredUser ID of the target.
messagerequiredMessage attached to the friend request.
createdAtrequiredRequest creation time (Unix ms).
ContactItem
shared.v1.ContactItemContactItem represents a single contact entry in a user's or agent's contact list. For users, a contact is created when a friend request is accepted. For agents, a contact is created when a user adds the agent.
BlockedUserItem
api.v1.BlockedUserItemBlockedUserItem represents a blocked user entry. When a user is blocked:
- Neither party can send messages or friend requests to the other.
- If the two users are contacts, the contact relationship is removed.
- Existing conversations are hidden but not deleted.
- A UserBlockToggledEvent is delivered to the blocker's update stream.
ConversationInfo
shared.v1.ConversationInfoConversationInfo represents a conversation entry in a user's or agent's conversation list. Covers private and group conversation types.
Sync model: Each conversation maintains an independent message_id sequence. Clients compare their local last_message_id with the server's value to determine whether incremental message fetching is needed.
Unread count: The server does NOT return unread_count. Clients calculate it locally: unread = last_message_id - last_read_message_id
conversationIdrequiredConversation ID (deterministic encoding, not auto-increment).
- PRIVATE: int64(max(a,b)) << 32 | int64(min(a,b))
- GROUP: int64(group_id)
isMutedrequiredWhether notifications are muted for this conversation.
lastMessageTimerequiredTimestamp of the last message (Unix ms). Only updated when a new message arrives, NOT when a message is deleted or edited. Also used as the sort key for conversation list ordering.
lastMessageIdrequiredLast message ID in this conversation (for incremental sync). Clients compare this with their local max message_id to determine whether to pull new messages. Also used for client-side unread calculation: unread = last_message_id - last_read_message_id
lastReadMessageIdrequiredCurrent user's last read message ID (read position).
peerIdrequiredPeer ID. For PRIVATE conversations this is the other user's ID (including agents). For GROUP conversations this is the group_id.
GroupInfo
shared.v1.GroupInfoGroupInfo is the shared group detail structure.
groupIdrequiredGroup ID. The group conversation_id equals int64(group_id).
namerequiredGroup display name.
avatarUrlrequiredGroup avatar URL.
descriptionrequiredGroup description text.
createdAtrequiredGroup creation time (Unix ms).
ownerIdrequiredGroup owner user ID.
MemberInfo
shared.v1.MemberInfoMemberInfo represents a group member (lightweight). User details (nickname, avatar, etc.) are provided separately via the users list in GetGroupInfoResponse or related_users in push events.
GroupContent
shared.v1.GroupContentGroupContent carries structured group event data as the body of a system message (MessageType = GROUP) delivered to a GROUP conversation. Each event type has its own strongly-typed payload.
The oneof field implicitly identifies the event type, so no separate enum is needed. Clients and agents switch on the oneof case to handle the appropriate in-chat system notification.
groupIdrequiredGroup ID this event belongs to.
eventMemberJoinedEvent
shared.v1.MemberJoinedEventMemberJoinedEvent is produced when members (users or agents) join a group. Supports batch joins: group creation and multi-invite produce a single event containing all member info, so clients render one "A, B, C joined" message instead of multiple individual messages. Delivered as a system message to the group conversation.
MemberLeftEvent
shared.v1.MemberLeftEventMemberLeftEvent is produced when a member voluntarily leaves a group. Delivered as a system message to the group conversation.
MemberRemovedEvent
shared.v1.MemberRemovedEventMemberRemovedEvent is produced when a member (user or agent) is removed by the group owner.
Delivery:
- GROUP conversation: visible to all remaining members.
- Removed member: receives a RemovedFromGroupEvent SnUpdate.
GroupInfoChangedEvent
shared.v1.GroupInfoChangedEventGroupInfoChangedEvent is produced when group metadata is updated (name, avatar, description, or other settings). Delivered as a system message to the group conversation. Clients obtain the latest values from related_groups in the push event.
MessageEntity
shared.v1.MessageEntityMessageEntity represents a rich text annotation using offset + length.
MentionEntity
shared.v1.MentionEntityMentionEntity carries @mention data.
userIdrequiredMentioned user ID.
isAllrequiredWhether this is an @all (mention everyone).
UrlEntity
shared.v1.UrlEntityUrlEntity carries URL link data.
urlrequiredURL address.
displayTextDisplay text override (optional).
PhoneEntity
shared.v1.PhoneEntityPhoneEntity carries phone number data.
phoneNumberrequiredPhone number.
HashtagEntity
shared.v1.HashtagEntityHashtagEntity carries hashtag data.
tagrequiredTag content (without # prefix).
MessageBody
shared.v1.MessageBodyMessageBody carries the message type and polymorphic content. All message content variants are included, enabling both users and agents to receive and parse every message type.
contentrecalledMessage recalled notification content.
customPayloadCustom binary payload for extensibility.
TextContent
shared.v1.TextContentTextContent carries plain text message data.
ImageContent
shared.v1.ImageContentImageContent carries image message data.
fileIdrequiredOriginal image file ID.
thumbnailFileIdrequiredThumbnail file ID.
widthrequiredImage width in pixels.
heightrequiredImage height in pixels.
sizeBytesrequiredFile size in bytes.
formatrequiredImage format (jpg/png/gif/webp).
AudioContent
shared.v1.AudioContentAudioContent carries audio/voice message data.
fileIdrequiredAudio file ID.
durationMsrequiredAudio duration in milliseconds.
sizeBytesrequiredFile size in bytes.
transcriptSpeech-to-text transcript (optional).
VideoContent
shared.v1.VideoContentVideoContent carries video message data.
fileIdrequiredVideo file ID.
thumbnailFileIdrequiredVideo thumbnail file ID.
durationMsrequiredVideo duration in milliseconds.
widthrequiredVideo width in pixels.
heightrequiredVideo height in pixels.
sizeBytesrequiredFile size in bytes.
FileContent
shared.v1.FileContentFileContent carries file attachment message data.
fileIdrequiredFile ID.
filenamerequiredFile name.
sizeBytesrequiredFile size in bytes.
mimeTyperequiredMIME type.
checksumSha256SHA256 checksum (optional).
MarkdownContent
shared.v1.MarkdownContentMarkdownContent carries markdown formatted message data.
CardContent
shared.v1.CardContentCardContent carries an Adaptive Card message payload. The card_json field contains a valid Adaptive Card JSON conforming to https://adaptivecards.io/schemas/adaptive-card.json
Server validates JSON syntax and enforces a 50 KB size limit, but does NOT validate against the Adaptive Card schema. Rendering is entirely the client's responsibility.
Interaction:
- Action.OpenUrl: handled client-side (open browser/webview).
- Action.Submit: client sends SubmitCardAction RPC with the action's data payload, which is forwarded to the agent.
- Action.ShowCard / Action.ToggleVisibility: handled client-side.
cardJsonrequiredAdaptive Card JSON payload. Must be valid JSON and ≤ 50 KB.
fallbackTextrequiredPlain text fallback for clients that cannot render Adaptive Cards.
StreamContent
shared.v1.StreamContentStreamContent carries streaming message data (e.g. agent real-time generation). Delivered via Update push on the long connection.
The stream is uniquely identified by message_id (assigned when the agent calls SendMessage with type=STREAM). Each delta carries a seq number for dedup and ordering.
Lifecycle: 1. START: A new streaming message is created (message_id assigned), StreamContent with phase=START is pushed. No text content yet. 2. DELTA: Incremental text fragments are pushed via Update. Deltas are ephemeral push-only payloads. 3. END: The final accumulated text is committed and phase=END is pushed with the complete content. 4. ERROR: Generation failed; the error state is committed.
Recovery after disconnect / app restart: Clients must track locally which stream messages have not reached a terminal phase (END or ERROR). On reconnect, call MessageService.GetMessage for each incomplete stream. The returned StreamContent reflects the current server-side state:
- phase=END + accumulated_text: generation finished while offline.
- phase=ERROR: generation failed.
- phase=DELTA: still generating; client resumes receiving deltas from the long connection. Use accumulated_text as the baseline and append subsequent deltas.
seqrequiredDelta sequence number (agent-side auto-increment, starting from 1). Used for dedup and ordering. Only meaningful in DELTA phase pushes; 0 for START/END/ERROR.
deltarequiredIncremental text fragment. Only meaningful in DELTA pushes.
contentTyperequiredContent MIME type (e.g. "text/plain", "text/markdown").
accumulatedTextrequiredAccumulated full text up to this point. Populated in: Empty in real-time DELTA pushes (clients accumulate locally).
- END phase: the complete final content.
- ERROR phase: partial content generated before the error.
- HTTP query (GetMessage): current accumulated content regardless of phase, enabling clients to recover after disconnect.
errorMessagerequiredError description. Only populated in ERROR phase.
ReplyContext
shared.v1.ReplyContextReplyContext defines the context of a replied message.
messageIdrequiredReplied message ID.
senderIdrequiredReplied message sender ID.
senderNicknamerequiredReplied message sender nickname.
contentPreviewrequiredContent preview of the replied message (first 100 chars).
MessageEnvelope
shared.v1.MessageEnvelopeMessageEnvelope is the unified message representation.
messageIdrequiredMessage ID (auto-increment within conversation).
conversationIdrequiredConversation ID.
senderIdrequiredSender ID (user_id or agent_user_id). For group event messages (GROUP), sender_id is 0. For recalled messages, sender_id remains the original sender.
createdAtrequiredMessage creation time (Unix ms).
updatedAtLast content modification time (Unix ms). Set on edit or recall.
editedrequiredWhether the message has been edited. Not set for recalls.
clientMessageIdrequiredClient-generated message ID for idempotency and dedup. Echoed from SendMessageRequest.client_message_id. Zero for system-generated messages (greetings, group events).
MetadataEntry
shared.v1.MessageEnvelope.MetadataEntrykeyrequiredvaluerequiredErrorDetail
shared.v1.ErrorDetailErrorDetail is the standard error payload attached to connect.Error details. It is the sole mechanism for clients to identify and handle errors.
MetadataEntry
shared.v1.ErrorDetail.MetadataEntrykeyrequiredvaluerequiredSnUpdate
shared.v1.SnUpdateSnUpdate is a sequenced update delivered to a user's update stream. Each update carries a monotonically increasing sn. Clients detect gaps by comparing received sn with local_sn + 1.
snrequiredSequence number in the user's update stream.
updateNonSnUpdate
shared.v1.NonSnUpdateNonSnUpdate is a non-sequenced, ephemeral update pushed in real time. Clients and agents do not track sn for these. Used for:
- Stream DELTA (real-time text generation push)
- CardActionAnswer (agent's toast/alert response to a card action)
- CardActionPayload (user's card action submission, delivered to agent)
CardActionAnswer
shared.v1.CardActionAnswerCardActionAnswer carries the agent's response to a card action. Pushed as NonSnUpdate to the user who submitted the action.
actionIdrequiredServer-assigned action ID (matches CardActionPayload.action_id).
conversationIdrequiredConversation where the card action was submitted.
messageIdrequiredCard message ID that the action belongs to.
agentUserIdrequiredAgent user ID that produced this answer.
textrequiredResponse text to display.
showAlertrequiredWhether to show as alert dialog (true) or toast (false).
CardActionPayload
shared.v1.CardActionPayloadCardActionPayload contains an Adaptive Card Action.Submit event. Pushed as NonSnUpdate to the agent that owns the card. This is an ephemeral event — not sequenced and not persisted.
actionIdrequiredServer-assigned action ID (use in AnswerCardAction).
conversationIdrequiredConversation ID.
messageIdrequiredCard message ID.
senderIdrequiredUser who submitted the action.
actionDatarequiredAction data (JSON string from Action.Submit).
verbrequiredAction verb identifier.
UpdateState
shared.v1.UpdateStateUpdateState holds the server's current sync state for a user.
latestSnrequiredThe latest sn in the user's update stream.
FriendRequestReceivedEvent
shared.v1.FriendRequestReceivedEventFriendRequestReceivedEvent is produced when a friend request is sent. Delivered as an SnUpdate to the target user's update stream. User info for from_user_id is provided via related_users in the Update wrapper.
FriendRequestAcceptedEvent
shared.v1.FriendRequestAcceptedEventFriendRequestAcceptedEvent is produced when a friend request is accepted. Delivered as an SnUpdate to both parties' update stream. User info for the peer is provided via related_users in the Update wrapper.
FriendRequestRejectedEvent
shared.v1.FriendRequestRejectedEventFriendRequestRejectedEvent is produced when a friend request is rejected. Delivered as an SnUpdate to both parties' update stream.
requestIdrequiredFriend request ID.
rejectorIdrequiredUser ID of the rejector.
FriendRequestSentEvent
shared.v1.FriendRequestSentEventFriendRequestSentEvent is produced when the current user sends a friend request. Delivered to the sender's own update stream (multi-device sync).
requestIdrequiredFriend request ID.
targetUserIdrequiredTarget user ID.
ContactAliasUpdatedEvent
shared.v1.ContactAliasUpdatedEventContactAliasUpdatedEvent is produced when a contact alias is changed. Delivered as an SnUpdate to the caller's own update stream only (multi-device sync).
contactUserIdrequiredContact user ID whose alias was changed.
newAliasrequiredNew alias value. Empty means alias was cleared.
UsernameChangedEvent
shared.v1.UsernameChangedEventUsernameChangedEvent is produced when a user sets their username. Delivered as an SnUpdate to the user's own update stream only (multi-device sync). Contacts learn the new username on demand.
userIdrequiredUser ID whose username changed.
newUsernamerequiredNew username.
ContactDeletedEvent
shared.v1.ContactDeletedEventContactDeletedEvent is produced when a contact is removed. Single-sided: delivered as an SnUpdate to the caller's own update stream only (multi-device sync). The other party is NOT notified.
peerUserIdrequiredUser ID of the removed contact.
ContactAddedEvent
shared.v1.ContactAddedEventContactAddedEvent is produced when a user adds an agent as a contact via AddContact. Delivered to the caller's own update stream (multi-device sync).
peerUserIdrequiredUser ID of the added agent.
UserBlockToggledEvent
shared.v1.UserBlockToggledEventUserBlockToggledEvent is produced when a user blocks or unblocks another. Delivered as an SnUpdate to the operator's own update stream only (for multi-device sync). The target user is NOT notified.
targetUserIdrequiredUser ID of the target user.
isBlockedrequiredTrue if blocked, false if unblocked.
UserProfileUpdatedEvent
shared.v1.UserProfileUpdatedEventUserProfileUpdatedEvent is produced when a user updates their profile. Delivered as an SnUpdate to the user's own update stream only (multi-device sync). Contacts refresh cached profile data on demand.
userIdrequiredUser ID whose profile was updated.
newNicknameChanged fields and their new values. Only changed fields are included.
newAvatarUrlNew avatar URL (present only if avatar was changed).
newSignatureNew signature (present only if signature was changed).
ConversationActionEvent
shared.v1.ConversationActionEventConversationActionEvent is produced when a user performs a conversation management action (mute, unmute, delete). Delivered as an SnUpdate to the operator's own update stream only (for multi-device sync). Other participants are NOT notified.
MessageDeletedEvent
shared.v1.MessageDeletedEventMessageDeletedEvent is produced when a user deletes messages locally (DeleteMessages or DeleteHistory). Delivered as an SnUpdate to the operator's own update stream only (for multi-device sync). Other participants are NOT notified.
conversationIdrequiredTarget conversation ID.
messageIdsrequired arrayDeleted message IDs. Empty when up_to_message_id is set.
upToMessageIdrequiredDelete all messages up to and including this ID. Zero when message_ids is set.
ReadReceiptEvent
shared.v1.ReadReceiptEventReadReceiptEvent is produced when a user marks messages as read. Delivered as an SnUpdate to the reader's own update stream only (for multi-device sync). The peer is NOT notified.
conversationIdrequiredConversation ID where messages were read.
readerUserIdrequiredUser ID of the reader.
lastReadMessageIdrequiredRead up to this message ID (inclusive).
readAtrequiredTimestamp when the read occurred (Unix ms).
RemovedFromGroupEvent
shared.v1.RemovedFromGroupEventRemovedFromGroupEvent is delivered as an SnUpdate to the removed member's (user or agent) update stream when they are removed from a group by the owner. Unified event for both user kicks and agent removals.
groupIdrequiredGroup ID the member was removed from.
operatorIdrequiredUser ID of the operator who performed the removal.
GroupDissolvedEvent
shared.v1.GroupDissolvedEventGroupDissolvedEvent is produced when the group owner dissolves the group. Delivered as an SnUpdate to every member's update stream.
operatorIdrequiredUser ID of the owner who dissolved the group.
groupIdrequiredGroup ID that was dissolved.
AgentStatusChangedEvent
shared.v1.AgentStatusChangedEventAgentStatusChangedEvent is produced when an agent is deleted by its developer. Delivered as an SnUpdate to all users who have added this agent as a contact.
ClientFrame
api.v1.ClientFrameClientFrame is the transport-agnostic upstream envelope used by both users and agents. The long connection is a push-only channel — clients send only authentication and heartbeat frames upstream. All business requests go through Connect RPC (HTTP).
requestIdrequiredClient-generated correlation ID. The server echoes this ID in the corresponding ServerFrame response so the client can match them.
payloadauthRequestConnection authentication. Server responds with ServerFrame.auth_response. Users send nxs_-prefixed tokens; agents send nxa_-prefixed tokens.
heartbeatPingHeartbeat ping. Server responds with ServerFrame.heartbeat_pong.
ServerFrame
api.v1.ServerFrameServerFrame is the transport-agnostic downstream envelope used by both users and agents. Every message sent by the server on the long connection is wrapped in a ServerFrame.
For response frames (auth_response, heartbeat_pong), the request_id matches the originating ClientFrame.request_id. For push frames (update, error), the server generates a unique request_id.
requestIdrequiredCorrelation ID. Echoed from ClientFrame.request_id for responses, server-generated for pushes.
payloadauthResponseAuthentication result. Response to ClientFrame.auth_request.
Update
api.v1.UpdateUpdate wraps an SnUpdate or NonSnUpdate with related entity info for client-side rendering without extra lookups.
HeartbeatPong
api.v1.HeartbeatPongHeartbeatPong is the server response to a HeartbeatPing.
serverTimerequiredServer timestamp (Unix ms). Clients can use this for clock drift estimation.
GatewayErrorFrame
api.v1.GatewayErrorFrameGatewayErrorFrame carries connection-level error information.
MediaFileInfo
shared.v1.MediaFileInfoMediaFileInfo contains file metadata returned after upload completion.
fileIdrequiredUnique file ID.
fileNamerequiredOriginal file name.
contentTyperequiredMIME content type.
sizerequiredFile size in bytes.
checksumrequiredFile checksum (SHA256).
widthrequiredImage/video width in pixels (0 if not applicable).
heightrequiredImage/video height in pixels (0 if not applicable).
durationMsrequiredAudio/video duration in milliseconds (0 if not applicable).
thumbnailFileIdrequiredThumbnail file ID (empty if not generated).
publicUrlrequiredPermanent public URL. Only populated when purpose is AVATAR or GROUP_AVATAR. Empty for MESSAGE purpose files.
AccountType
shared.v1.AccountTypeAccountType distinguishes human users from agent accounts.
| Value | Number | Description |
|---|---|---|
ACCOUNT_TYPE_UNSPECIFIED | 0 | Unspecified. |
ACCOUNT_TYPE_USER | 1 | Human user. |
ACCOUNT_TYPE_AGENT | 2 | Agent. |
DeviceType
shared.v1.DeviceTypeDeviceType defines client device platforms.
| Value | Number | Description |
|---|---|---|
DEVICE_TYPE_UNSPECIFIED | 0 | Unspecified. |
DEVICE_TYPE_IOS | 1 | iOS device. |
DEVICE_TYPE_ANDROID | 2 | Android device. |
DEVICE_TYPE_WEB | 3 | Web browser. |
DEVICE_TYPE_DESKTOP | 4 | Desktop application. |
DEVICE_TYPE_CLI | 5 | CLI / TUI terminal client. |
PushPlatform
shared.v1.PushPlatformPushPlatform defines push notification platforms. Only APNs and FCM are supported in the current version. Desktop clients (Tauri) rely on the in-app long connection.
| Value | Number | Description |
|---|---|---|
PUSH_PLATFORM_UNSPECIFIED | 0 | Unspecified. |
PUSH_PLATFORM_APNS | 1 | Apple Push Notification service. |
PUSH_PLATFORM_FCM | 2 | Firebase Cloud Messaging. |
AgentStatus
shared.v1.AgentStatusAgentStatus defines agent lifecycle states.
| Value | Number | Description |
|---|---|---|
AGENT_STATUS_UNSPECIFIED | 0 | Unspecified. |
AGENT_STATUS_ACTIVE | 1 | Agent is active and operational. |
AGENT_STATUS_DELETED | 2 | Agent has been permanently deleted (soft-delete). |
AgentVisibility
shared.v1.AgentVisibilityAgentVisibility defines agent discoverability and addability.
| Value | Number | Description |
|---|---|---|
AGENT_VISIBILITY_UNSPECIFIED | 0 | Unspecified. |
AGENT_VISIBILITY_PUBLIC | 1 | Agent is publicly listed in the directory. Any user can search and add. |
AGENT_VISIBILITY_PRIVATE | 2 | Agent is private. Only the creator can use it (auto-added on creation). |
AgentDeliveryMode
shared.v1.AgentDeliveryModeAgentDeliveryMode defines the event delivery mode for an agent.
| Value | Number | Description |
|---|---|---|
AGENT_DELIVERY_MODE_UNSPECIFIED | 0 | Unspecified. |
AGENT_DELIVERY_MODE_WEBHOOK | 1 | Events delivered via Webhook HTTP POST. |
AGENT_DELIVERY_MODE_WEBSOCKET | 2 | Events delivered via WebSocket push. |
IdentityType
api.v1.IdentityTypeIdentityType defines authentication identity types.
| Value | Number | Description |
|---|---|---|
IDENTITY_TYPE_UNSPECIFIED | 0 | Unspecified. |
IDENTITY_TYPE_EMAIL | 1 | Email address. |
IDENTITY_TYPE_PHONE | 2 | Phone number. |
FriendRequestStatus
shared.v1.FriendRequestStatusFriendRequestStatus defines friend request lifecycle states.
| Value | Number | Description |
|---|---|---|
FRIEND_REQUEST_STATUS_UNSPECIFIED | 0 | Unspecified. |
FRIEND_REQUEST_STATUS_PENDING | 1 | Request is pending review. |
FRIEND_REQUEST_STATUS_ACCEPTED | 2 | Request has been accepted. |
FRIEND_REQUEST_STATUS_REJECTED | 3 | Request has been rejected. |
SearchAccountType
api.v1.SearchAccountTypeSearchAccountType specifies which account types to include in search.
| Value | Number | Description |
|---|---|---|
SEARCH_ACCOUNT_TYPE_UNSPECIFIED | 0 | Unspecified. Searches all account types (users and agents). |
SEARCH_ACCOUNT_TYPE_USER | 1 | Search only human users. |
SEARCH_ACCOUNT_TYPE_AGENT | 2 | Search only agents. |
ConversationType
shared.v1.ConversationTypeConversationType defines conversation types.
| Value | Number | Description |
|---|---|---|
CONVERSATION_TYPE_UNSPECIFIED | 0 | Unspecified. |
CONVERSATION_TYPE_PRIVATE | 1 | Private one-on-one chat. |
CONVERSATION_TYPE_GROUP | 2 | Group chat. |
ConversationActionType
shared.v1.ConversationActionTypeConversationActionType defines conversation management actions.
| Value | Number | Description |
|---|---|---|
CONVERSATION_ACTION_TYPE_UNSPECIFIED | 0 | Unspecified. |
CONVERSATION_ACTION_TYPE_MUTE | 1 | Mute conversation notifications. |
CONVERSATION_ACTION_TYPE_UNMUTE | 2 | Unmute conversation notifications. |
CONVERSATION_ACTION_TYPE_DELETE | 3 | Soft-delete conversation from the user's list. See UpdateConversationAction RPC for the full delete lifecycle. |
MemberRole
shared.v1.MemberRoleMemberRole defines group member roles.
| Value | Number | Description |
|---|---|---|
MEMBER_ROLE_UNSPECIFIED | 0 | Unspecified. |
MEMBER_ROLE_OWNER | 1 | Group owner. |
MEMBER_ROLE_MEMBER | 2 | Regular member. |
GroupStatus
shared.v1.GroupStatusGroupStatus defines group lifecycle states.
| Value | Number | Description |
|---|---|---|
GROUP_STATUS_UNSPECIFIED | 0 | Unspecified. |
GROUP_STATUS_NORMAL | 1 | Group is active and operational. |
GROUP_STATUS_DISSOLVED | 2 | Group has been dissolved. |
MessageType
shared.v1.MessageTypeMessageType defines message content types.
| Value | Number | Description |
|---|---|---|
MESSAGE_TYPE_UNSPECIFIED | 0 | Unspecified. |
MESSAGE_TYPE_TEXT | 1 | Plain text message. |
MESSAGE_TYPE_IMAGE | 2 | Image message. |
MESSAGE_TYPE_AUDIO | 3 | Audio/voice message. |
MESSAGE_TYPE_VIDEO | 4 | Video message. |
MESSAGE_TYPE_FILE | 5 | File attachment message. |
MESSAGE_TYPE_MARKDOWN | 6 | Markdown formatted message. |
MESSAGE_TYPE_CARD | 7 | Interactive card message. |
MESSAGE_TYPE_STREAM | 8 | Streaming message. |
MESSAGE_TYPE_GROUP | 10 | Group event message (delivered to group conversation). |
MESSAGE_TYPE_RECALLED | 11 | Message recalled notification. |
MessageEntityType
shared.v1.MessageEntityTypeMessageEntityType defines rich text annotation types.
| Value | Number | Description |
|---|---|---|
MESSAGE_ENTITY_TYPE_UNSPECIFIED | 0 | Unspecified. |
MESSAGE_ENTITY_TYPE_MENTION | 1 | @mention. |
MESSAGE_ENTITY_TYPE_URL | 2 | URL link. |
MESSAGE_ENTITY_TYPE_PHONE | 3 | Phone number. |
MESSAGE_ENTITY_TYPE_HASHTAG | 4 | Hashtag. |
MESSAGE_ENTITY_TYPE_EMAIL | 5 | Email address. |
MESSAGE_ENTITY_TYPE_BOLD | 6 | Bold text. |
MESSAGE_ENTITY_TYPE_ITALIC | 7 | Italic text. |
MESSAGE_ENTITY_TYPE_CODE | 8 | Code span. |
StreamPhase
shared.v1.StreamPhaseStreamPhase defines streaming message lifecycle phases.
| Value | Number | Description |
|---|---|---|
STREAM_PHASE_UNSPECIFIED | 0 | Unspecified. |
STREAM_PHASE_START | 1 | Stream started. |
STREAM_PHASE_DELTA | 2 | Incremental delta. |
STREAM_PHASE_END | 3 | Stream completed. |
STREAM_PHASE_ERROR | 4 | Stream error. |
ClientFrameType
api.v1.ClientFrameTypeClientFrameType enumerates upstream frame types.
| Value | Number | Description |
|---|---|---|
CLIENT_FRAME_TYPE_UNSPECIFIED | 0 | Unspecified. |
CLIENT_FRAME_TYPE_AUTH_REQUEST | 1 | Connection authentication. |
CLIENT_FRAME_TYPE_HEARTBEAT_PING | 2 | Heartbeat ping. |
ServerFrameType
api.v1.ServerFrameTypeServerFrameType enumerates downstream frame types.
| Value | Number | Description |
|---|---|---|
SERVER_FRAME_TYPE_UNSPECIFIED | 0 | Unspecified. |
SERVER_FRAME_TYPE_AUTH_RESPONSE | 1 | Authentication result. |
SERVER_FRAME_TYPE_UPDATE | 2 | Update push (sequenced or non-sequenced). |
SERVER_FRAME_TYPE_HEARTBEAT_PONG | 3 | Heartbeat pong. |
SERVER_FRAME_TYPE_ERROR | 4 | Error. |
MediaPurpose
shared.v1.MediaPurposeMediaPurpose defines the intended usage of an uploaded file, which determines storage visibility and URL generation strategy.
| Value | Number | Description |
|---|---|---|
MEDIA_PURPOSE_UNSPECIFIED | 0 | Unspecified. |
MEDIA_PURPOSE_AVATAR | 1 | User avatar (public, permanent URL). |
MEDIA_PURPOSE_GROUP_AVATAR | 2 | Group avatar (public, permanent URL). |
MEDIA_PURPOSE_MESSAGE | 3 | Message attachment (private, time-limited URL with conversation-level access control). |