Base URL (dev): http://localhost:5000/api
All responses use the shape { success: boolean, data?: any, message?: string }.
Authenticated routes require the header: Authorization: Bearer <JWT>.
Register a new user. Public.
Body
{ "name": "Jane Doe", "email": "jane@example.com", "password": "secret123", "role": "customer" }role may only be customer or agent on public signup (admins are created directly in the DB or seed script).
Response 201
{ "success": true, "data": { "_id": "...", "name": "Jane Doe", "email": "jane@example.com", "role": "customer", "token": "<jwt>" } }Body: { "email": "...", "password": "..." }
Response 200: same shape as register.
Returns the authenticated user's profile (no password field).
Create a ticket. Any authenticated user.
Body: { "subject": "...", "description": "...", "priority": "medium", "category": "general" }
List tickets. Customers see only their own; agents/admins see all. Supports filtering and pagination.
Response: { success, data: [...tickets], pagination: { total, page, pages } }
Get a single ticket with populated createdBy, assignedTo, and replies.sender.
Customers can only fetch their own tickets (403 otherwise).
Update status, priority, category, or assignedTo. Setting status to resolved/closed stamps resolvedAt.
Add a reply to the ticket thread. Auto-transitions status from open to in_progress.
Body: { "message": "..." }
Deletes a ticket.
Starts a new AI conversation for the logged-in customer and returns a welcome bot message.
Sends a message. If the conversation is still in bot mode, the message is forwarded to
OpenAI and the assistant's reply is returned. If the AI's reply contains an internal
[ESCALATE] marker, the platform automatically:
- Creates a new ticket (
source: "ai_escalation"). - Switches the conversation's
modetolive. - Returns the created ticket in the response so the UI can notify the customer.
If the conversation is already in live mode, no AI call is made — use the Socket.io
events documented below instead.
Response 201
{
"success": true,
"data": {
"userMessage": { "...": "..." },
"botMessage": { "...": "..." } ,
"escalatedTicket": null
}
}Returns the full ordered message history for a conversation.
List conversations. Customers see their own; agents/admins see active live conversations waiting for or in a chat.
Agent claims a live conversation (sets agent field and mode: "live").
Returns { totalTickets, openTickets, resolvedTickets, totalConversations, totalCustomers, avgResolutionHours }.
Returns [{ _id: "open", count: 12 }, ...] — ticket counts grouped by status.
Returns daily ticket counts for the last 14 days: [{ _id: "2026-06-20", count: 3 }, ...].
Returns ticket counts grouped by category.
Connect with: io(URL, { auth: { token: '<jwt>' } })
| Event (client → server) | Payload | Description |
|---|---|---|
join_conversation |
conversationId |
Joins the room for that conversation |
send_message |
{ conversationId, content } |
Sends a live chat message; persisted and broadcast |
typing |
{ conversationId, isTyping } |
Broadcasts a typing indicator to the room |
| Event (server → client) | Payload | Description |
|---|---|---|
new_message |
Message object | Broadcast to all sockets in the conversation room |
user_typing |
{ userId, name, isTyping } |
Typing indicator from the other participant |
error_message |
{ message } |
Sent to the sender if persisting a message fails |
All errors follow: { "success": false, "message": "Human readable error" } with an
appropriate HTTP status code (400, 401, 403, 404, 500).