SecondMeSecondMe API
SecondMe API

Visitor Chat

Real-time conversations with SecondMe avatars for authenticated and anonymous users

Visitor Chat enables third-party applications to integrate SecondMe avatar conversations. Your users (logged in or anonymous) can chat with any SecondMe avatar in real time.

Base URL: https://api.mindverse.com/gate/lab


Overview

Visitor Chat works in two steps:

  1. Initialize (/visitor-chat/init) — Authenticate + create session + return WebSocket credentials
  2. Send message (/visitor-chat/send) — Send text messages, AI replies are pushed via WebSocket

Identity Modes

ModeAuthenticationUse Case
Authenticated userOAuth2 authorization_code TokenUser already logged in via OAuth
Anonymous userOAuth2 client_credentials TokenNo login required, your backend acts on behalf of users

Authenticated User Flow (2 steps)

User already has an OAuth access token:

1. POST /visitor-chat/init (Authorization: Bearer user_token, body: {apiKey})
2. POST /visitor-chat/send (Authorization: Bearer user_token, body: {sessionId, message})

Anonymous User Flow (3 steps)

Your backend first gets an app-level token, then calls on behalf of anonymous users:

1. POST /oauth/token/client  → Get app token (cacheable for 7 days)
2. POST /visitor-chat/init   → Initialize chat (visitorId required)
3. POST /visitor-chat/send   → Send messages

Important: client_secret must only be used on your backend. Never expose it to the frontend. Anonymous users' frontends should call through your backend API proxy.


Get App Token (Anonymous mode only)

Use the client_credentials grant to get an app-level access token. This token represents your application (not a specific user) and is used for anonymous user scenarios.

POST /api/oauth/token/client

Request

curl -X POST "https://api.mindverse.com/gate/lab/api/oauth/token/client" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&scope=chat.write"

Response

{
  "code": 0,
  "data": {
    "accessToken": "lba_at_xxx...",
    "tokenType": "Bearer",
    "expiresIn": 604800,
    "scope": ["chat.write"]
  }
}
FieldDescription
accessTokenApp token, used for subsequent init and send calls
expiresInValidity period (seconds), recommend caching and re-requesting on expiry

Initialize Chat

Create a visitor chat session and get WebSocket connection credentials.

POST /api/secondme/visitor-chat/init

Authentication

Requires OAuth2 Token (authorization_code or client_credentials).

Request Headers

HeaderRequiredDescription
AuthorizationYesBearer Token
Content-TypeYesapplication/json

Request Parameters

ParameterTypeRequiredDescription
apiKeystringYesAvatar API Key (sk- prefix)
visitorIdstringConditionalAnonymous user identifier, required for client_credentials auth. Alphanumeric, underscores, and hyphens only, max 128 chars
visitorNamestringNoVisitor display name (optional for anonymous mode), shown in avatar hub conversation list, max 200 chars

Request Examples

Authenticated user:

curl -X POST "https://api.mindverse.com/gate/lab/api/secondme/visitor-chat/init" \
  -H "Authorization: Bearer lba_at_user_access_token" \
  -H "Content-Type: application/json" \
  -d '{
    "apiKey": "sk-your-avatar-api-key"
  }'

Anonymous user:

curl -X POST "https://api.mindverse.com/gate/lab/api/secondme/visitor-chat/init" \
  -H "Authorization: Bearer lba_at_app_token" \
  -H "Content-Type: application/json" \
  -d '{
    "apiKey": "sk-your-avatar-api-key",
    "visitorId": "device_abc123",
    "visitorName": "John"
  }'

The avatar hub displays {visitorName}({appName}) (e.g. "John(My App)"), where appName is automatically fetched from your app registration info.

Response

{
  "code": 0,
  "data": {
    "sessionId": "6ff56704-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "wsUrl": "wss://ws.mindos.com/os/ws?wsId=...&authBody=...",
    "avatarName": "My Avatar",
    "opening": "Hello! How can I help?"
  }
}
FieldTypeDescription
sessionIdstringSession ID (used for sending messages)
wsUrlstringFull WebSocket URL (includes auth params, connect directly)
avatarNamestringAvatar display name
openingstring | nullAvatar greeting message

Send Message

Send a text message to the current session. AI replies are pushed via WebSocket.

POST /api/secondme/visitor-chat/send

Authentication

Requires OAuth2 Token (same token used for init).

Request Parameters

ParameterTypeRequiredDescription
sessionIdstringYesSession ID (from init response)
apiKeystringYesAvatar API Key (same as init)
messagestringYesMessage content, 1-10000 characters

When tokens expire or cache is lost, /send automatically recovers the session using the current OAuth token + apiKey. No need to call /init again.

Request Example

curl -X POST "https://api.mindverse.com/gate/lab/api/secondme/visitor-chat/send" \
  -H "Authorization: Bearer lba_at_your_token" \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "6ff56704-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "apiKey": "sk-your-avatar-api-key",
    "message": "Hello, who are you?"
  }'

Response

{
  "code": 0,
  "data": {
    "sent": true
  }
}

WebSocket Message Format

After connecting to wsUrl, AI replies are pushed via WebSocket.

AI Reply Message

{
  "sender": "umm",
  "sessionId": "6ff56704-...",
  "index": 0,
  "multipleData": [{
    "singleDataType": "text",
    "modal": {
      "answer": "Hello! I am..."
    }
  }]
}
FieldDescription
sender"umm" indicates AI reply
index0, 1, 2, ... are intermediate chunks (streaming), -1 means reply complete
multipleData[0].modal.answerAI reply text

Message Assembly Logic

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.sender !== "umm") return;

  if (msg.index === -1) {
    // Reply complete
    return;
  }

  const text = msg.multipleData?.[0]?.modal?.answer;
  if (text) {
    // Append to current reply
    currentReply += text;
  }
};

Complete Example

// 1. Initialize
const initRes = await fetch("/api/secondme/visitor-chat/init", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ apiKey: "sk-xxx" }),
});
const { data } = await initRes.json();

// 2. Connect WebSocket
const ws = new WebSocket(data.wsUrl);
ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.sender === "umm" && msg.index !== -1) {
    console.log("AI:", msg.multipleData?.[0]?.modal?.answer);
  }
};

// 3. Send message
await fetch("/api/secondme/visitor-chat/send", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    sessionId: data.sessionId,
    apiKey: "sk-xxx",
    message: "Hello",
  }),
});

Error Codes

Error CodeHTTP StatusDescription
visitor_chat.visitor_id_required400Missing visitorId in anonymous mode
visitor_chat.session_not_found400Session not initialized or expired, call /init again
visitor_chat.session_expired400Session cache expired, call /init again
oauth2.invalid_client401Invalid client_id or client_secret
open.api.key.not.found401Invalid avatar API Key
open.api.user.not.found404User not found

FAQ

What can I use as visitorId?

Any persistent user identifier — device fingerprint, your own database user ID, or a random UUID. The same visitorId reuses existing sessions.

Should I cache the client_credentials token?

Yes, recommended. Valid for 7 days, just re-request when expired.

What if the WebSocket disconnects?

Call visitor-chat/init again to get a new wsUrl (session will be reused).