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:
- Initialize (
/visitor-chat/init) — Authenticate + create session + return WebSocket credentials - Send message (
/visitor-chat/send) — Send text messages, AI replies are pushed via WebSocket
Identity Modes
| Mode | Authentication | Use Case |
|---|---|---|
| Authenticated user | OAuth2 authorization_code Token | User already logged in via OAuth |
| Anonymous user | OAuth2 client_credentials Token | No 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 messagesImportant:
client_secretmust 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/clientRequest
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"]
}
}| Field | Description |
|---|---|
| accessToken | App token, used for subsequent init and send calls |
| expiresIn | Validity 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/initAuthentication
Requires OAuth2 Token (authorization_code or client_credentials).
Request Headers
| Header | Required | Description |
|---|---|---|
| Authorization | Yes | Bearer Token |
| Content-Type | Yes | application/json |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| apiKey | string | Yes | Avatar API Key (sk- prefix) |
| visitorId | string | Conditional | Anonymous user identifier, required for client_credentials auth. Alphanumeric, underscores, and hyphens only, max 128 chars |
| visitorName | string | No | Visitor 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)"), whereappNameis 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?"
}
}| Field | Type | Description |
|---|---|---|
| sessionId | string | Session ID (used for sending messages) |
| wsUrl | string | Full WebSocket URL (includes auth params, connect directly) |
| avatarName | string | Avatar display name |
| opening | string | null | Avatar greeting message |
Send Message
Send a text message to the current session. AI replies are pushed via WebSocket.
POST /api/secondme/visitor-chat/sendAuthentication
Requires OAuth2 Token (same token used for init).
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| sessionId | string | Yes | Session ID (from init response) |
| apiKey | string | Yes | Avatar API Key (same as init) |
| message | string | Yes | Message content, 1-10000 characters |
When tokens expire or cache is lost,
/sendautomatically recovers the session using the current OAuth token + apiKey. No need to call/initagain.
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..."
}
}]
}| Field | Description |
|---|---|
| sender | "umm" indicates AI reply |
| index | 0, 1, 2, ... are intermediate chunks (streaming), -1 means reply complete |
| multipleData[0].modal.answer | AI 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 Code | HTTP Status | Description |
|---|---|---|
| visitor_chat.visitor_id_required | 400 | Missing visitorId in anonymous mode |
| visitor_chat.session_not_found | 400 | Session not initialized or expired, call /init again |
| visitor_chat.session_expired | 400 | Session cache expired, call /init again |
| oauth2.invalid_client | 401 | Invalid client_id or client_secret |
| open.api.key.not.found | 401 | Invalid avatar API Key |
| open.api.user.not.found | 404 | User 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).