Thirty tools — the largest namespace — cover chatbot configuration, channels (including WhatsApp), live session handling, custom tools, analytics, and the audit log.
All endpoints follow the pattern POST https://mcp.vlozi.app/tools/chat.<name> with Authorization: Bearer ls_xxx. All responses use this envelope:
{ "data": <payload>, "error": null } // success
{ "data": null, "error": "..." } // failure (with HTTP 4xx/5xx)NOTE
bot_id is optional on almost every tool — omit it and Vlozi resolves your workspace's oldest bot (the "default bot", auto-created on first use if you have none). A workspace can have up to 10 bots.
Config
chat.list_bots
List your bots.
Scope: chatbot:config.read
Input: none
Response: { bots: { id, name, activeModel, memoryTier, archivedAt, createdAt, channelCount }[] }, oldest first (the first item is the default bot)
chat.create_bot
Create a new bot.
Scope: chatbot:config.write
Input: { name: string } (1–100 chars)
Response: { bot: BotConfig }
NOTE
Capped at 10 bots per workspace — returns 403 at the limit.
chat.delete_bot
Delete a bot and everything under it.
Scope: chatbot:config.write
Input: { bot_id: string } (required)
Response: { deleted: true }
CAUTION
Cascades explicitly through messages → tool calls → sessions → custom tools → channels → the bot itself. Irreversible. Returns 409 ("You can't delete your only bot") if it's the workspace's last one.
chat.get_config
Get a bot's full configuration.
Scope: chatbot:config.read
Input: { bot_id?: string }
Response: { config: BotConfig } — includes personaPrompt, fallbackMessage, handoffTrigger (default "[[HANDOFF]]"), activeModel, creativity, memoryTier, historyWindow, summaryEnabled/summaryDepth, tone, responseLength, language, crisisDetectionEnabled, knowledgeEnabled, leadCaptureRules, welcomeMessage, offlineMessage
chat.update_config
Update a bot's configuration. Pass only the fields to change.
Scope: chatbot:config.write
Input:
| Field | Type | Notes |
|---|---|---|
bot_id |
string |
|
name |
string |
≤100 chars |
persona_prompt |
string |
≤5000 chars |
fallback_message |
string |
≤500 chars |
active_model |
"haiku" | "sonnet" | "opus" |
|
creativity |
number |
0–1 |
memory_tier |
number |
0–4 |
history_window |
number |
1–50 |
summary_enabled |
boolean |
|
summary_depth |
"brief" | "detailed" |
|
tone |
"formal" | "casual" | "friendly" | "professional" |
|
response_length |
"brief" | "balanced" | "detailed" |
|
language |
see note | |
crisis_detection_enabled |
boolean |
|
knowledge_enabled |
boolean |
|
welcome_message |
string |
≤500 chars |
offline_message |
string |
≤500 chars |
lead_capture_rules |
{ trigger, askFor, message }[] |
Response: { config: BotConfig } (full updated row)
NOTE
language accepts auto, en, hi, bn, ta, te, mr, gu, kn, ml, pa, ur, es, fr, ar, pt, de, zh. Every change is diffed and recorded in the audit log.
Channels
chat.list_channels
List connected channels.
Scope: chatbot:channels.read
Input: { bot_id?: string } — omit for every channel across the whole workspace (a different default than the config tools, which scope to one bot)
Response: { channels: { id, botId, platform, identifier, status, widgetToken, createdAt }[] } — status is active | paused | auth_failed | rate_limited (the last two are set internally, not settable via any tool)
chat.add_channel
Connect a channel to a bot.
Scope: chatbot:channels.write
Input: { platform: "website_widget" | "whatsapp" | "telegram" | "instagram", identifier: string, credentials?: string, bot_token?: string, bot_id?: string } (platform/identifier required, identifier 1–500 chars)
Response: { channel: { id, platform, identifier, status: "active", widget_token?, embed_script? } }
NOTE
website_widget generates a widget_token and an embed_script <script> tag ready to paste into a page. telegram with a bot_token validates it against Telegram's API, renames identifier to the bot's @username, and registers a webhook (best-effort — failure to register still leaves the channel connected). Adding a channel is idempotent per (platform, identifier) — reconnecting the same messaging identity updates the existing row (fresh credentials, status: "active") instead of creating a duplicate. Use chat.connect_whatsapp for WhatsApp, not this tool.
chat.connect_whatsapp
Complete a WhatsApp Business connection via Meta's Embedded Signup flow.
Scope: chatbot:channels.write
Input: { code: string, phone_number_id: string, waba_id: string, bot_id?: string } (code/phone_number_id/waba_id required)
Response: { channel: { id, platform: "whatsapp", identifier: string, verified_name: string, status: "active", webhook_subscribed: boolean } }
NOTE
This is the callback step after Meta's Embedded Signup popup returns code/phone_number_id/waba_id to your frontend — there's no QR code involved. It exchanges the code for a long-lived token, validates the phone number, and subscribes the webhook (a failed webhook subscription is non-fatal — the channel connects anyway with webhook_subscribed: false).
chat.delete_channel
Disconnect a channel.
Scope: chatbot:channels.write
Input: { id: string } (required)
Response: { deleted: true }
NOTE
Sessions and messages already on this channel are untouched. For Telegram, this also best-effort tears down the registered webhook.
chat.set_channel_status
Pause or reactivate a channel.
Scope: chatbot:channels.write
Input: { id: string, status: "active" | "paused" } (both required)
Response: { updated: true, status: string }
Sessions
Sessions move ai_handled → human_escalated → closed (terminal — no reopen tool exists). assign_session/reply_to_session don't change status, just claim/answer the session.
chat.list_sessions
List sessions.
Scope: chatbot:sessions.read
Input: { bot_id?: string, status?: "ai_handled" | "human_escalated" | "closed" | "all", limit?: number, offset?: number } (status default all; limit 1–100 default 20)
Response: { sessions: { id, channelId, contactId, status, initiatedBy, assignedAgentId, messageCount, slaBreachAt, lastMessageAt, createdAt }[] }, newest message first
chat.get_session_messages
Get a session's transcript.
Scope: chatbot:sessions.read
Input: { session_id: string, limit?: number, before?: string } (session_id required; limit 1–100 default 50; before = a message id cursor)
Response: { messages: { id, role, content, intent, model, latencyMs, createdAt }[], has_more: boolean } — oldest to newest
chat.reply_to_session
Send a message as a human agent.
Scope: chatbot:sessions.write
Input: { session_id: string, content: string } (both required; content 1–4000 chars)
Response: { message: { id, role: "agent", content } }
NOTE
Self-assigns the session to you and clears its SLA timer — doesn't change status.
chat.assign_session
Assign a session to an agent.
Scope: chatbot:sessions.write
Input: { session_id: string, agent_id: string } (both required)
Response: { assigned: true, agent_id: string }
NOTE
No error on an unknown session — it's silently a no-op, matching the dashboard's own behavior.
chat.resolve_session
Close a session.
Scope: chatbot:sessions.write
Input: { session_id: string, summary?: string }
Response: { resolved: true }
NOTE
Terminal — sets status: "closed". If the session is linked to a contact, this also triggers Contact Intelligence to extract memories from the transcript in the background.
chat.escalate_session
Hand a session to a human.
Scope: chatbot:sessions.write
Input: { session_id: string, reason?: string } (session_id required)
Response: { escalated: true, status: "human_escalated" }
NOTE
Arms a 15-minute SLA timer. Unlike the other session-mutating tools, this one does not write an audit-log entry (only an internal event).
chat.summarize_session
AI-summarize a session's transcript.
Scope: chatbot:sessions.read
Input: { session_id: string } (required)
Response: { summary: string, creditsCharged: number }
IMPORTANT
Charges a flat 5-credit fee, refunded on failure. Always runs on the haiku model regardless of the bot's own configured active_model. Returns 422 if the session has no messages yet, 503 if no AI model key is configured.
chat.draft_reply
AI-draft the next agent reply — does not send it. Pair with reply_to_session.
Scope: chatbot:sessions.read
Input: { session_id: string, brief?: string } (brief = optional guidance on what to say)
Response: { reply: string, creditsCharged: number }
IMPORTANT
Charges a flat 5-credit fee, refunded on failure. Same model pinning and error conditions as summarize_session.
Custom tools
A "custom tool" is a tenant-registered webhook-backed function the bot's LLM can call mid-conversation — you provide a name, a JSON-Schema parameter spec, and a URL; Vlozi POSTs the arguments there and feeds the result back to the model.
chat.list_custom_tools
List a bot's custom tools.
Scope: chatbot:config.read
Input: { bot_id?: string }
Response: { tools: { id, name, description, parameters, webhook_url, has_auth: boolean, timeout_ms, enabled, created_at, updated_at }[] }
NOTE
has_auth reports whether an auth header is configured — the actual secret is never returned.
chat.list_tool_calls
List recent custom-tool invocations.
Scope: chatbot:config.read
Input: { bot_id?: string, tool?: string, session?: string, limit?: number, offset?: number } (limit 1–100 default 50)
Response: { calls: { id, sessionId, messageId, toolName, parameters, result, success, latencyMs, error, createdAt }[] }
chat.create_custom_tool
Register a new custom tool.
Scope: chatbot:config.write
Input:
| Field | Type | Notes |
|---|---|---|
name |
string (required) |
Lowercase snake_case, ≤64 chars |
description |
string (required) |
1–1000 chars — the LLM reads this to decide when to call it |
parameters |
object (required) |
JSON Schema for the arguments |
webhook_url |
string (required) |
Must be a public https URL |
auth_header |
string |
Stored encrypted, sent as Authorization |
timeout_ms |
number |
100–30000, default 5000 |
enabled |
boolean |
Default true |
bot_id |
string |
Response: { tool: CustomToolDTO }
WARNING
webhook_url is rejected if it points at localhost, a private/internal IP range, or a cloud metadata endpoint — public https only. Returns 409 on a duplicate name within the same bot.
chat.update_custom_tool
Update a custom tool. Pass only the fields to change.
Scope: chatbot:config.write
Input: { id: string } plus any subset of create_custom_tool's fields
Response: { tool: CustomToolDTO }
chat.delete_custom_tool
Delete a custom tool.
Scope: chatbot:config.write
Input: { id: string } (required)
Response: { deleted: true }
Analytics
All six accept { bot_id?: string, days?: number } (days 1–365, default 30) and compute live from session/message data — nothing here is a stub.
chat.analytics_overview
Scope: chatbot:sessions.read
Response: { days, total_sessions, resolved, escalated, open, resolution_rate, escalation_rate, avg_messages_per_session, sla_breach_rate }
chat.analytics_intents
Scope: chatbot:sessions.read
Response: { intents: { intent, count }[] }, most common first
chat.analytics_models
Scope: chatbot:sessions.read
Response: { models: { model, count, avg_latency_ms, p95_latency_ms }[] }
chat.analytics_sessions_breakdown
Scope: chatbot:sessions.read
Response: { breakdown: { initiated_by, status, count }[] } — the full initiatedBy × status matrix
chat.analytics_tools
Scope: chatbot:sessions.read
Response: { tools: { tool_name, calls, success_rate, avg_latency_ms }[] }
chat.analytics_timeseries
Scope: chatbot:sessions.read
Response: { messages: { day, count }[], sessions: { day, count }[] } — two daily series, for sparklines
Audit
chat.get_audit_log
List config/session changes for a bot.
Scope: chatbot:config.read
Input: { bot_id?: string, action?: string, limit?: number, offset?: number } (limit 1–100 default 50)
Response: { entries: { id, botId, actorId, action, targetEntity, targetId, diff, createdAt }[] }
NOTE
action is one of bot_created, bot_deleted, bot_config_updated, channel_connected, channel_disconnected, channel_status_changed, session_assigned, session_resolved, tool_created, tool_updated, tool_deleted. Escalating a session does not appear here (see escalate_session above).