Parent: Chat Service
Endpoints Summary
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/chats | JWT | Get all chats for authenticated user |
| GET | /api/chats/:chatId | JWT | Get single chat details |
| POST | /api/chats/start | JWT | Start new chat (customers only) |
| GET | /api/chats/:chatId/messages | JWT | Get messages (paginated) |
| POST | /api/chats/:chatId/messages | JWT | Send message (text + images) |
| POST | /api/chats/:chatId/messages/:messageId/read | JWT | Mark message as read |
| GET | /health | None | Health check |
REST API
Get All Chats
GET /api/chats
Auth: JWT (Cognito or Django)
Returns all chat sessions for the authenticated user, ordered by most recent activity.
Response (200):
{
"chats": [
{
"id": 123,
"customer": {
"id": 1,
"name": "John Doe"
},
"dentist": {
"id": 5,
"name": "Dr. Smith"
},
"lastMessage": {
"content": "Thank you!",
"createdAt": "2026-02-13T10:30:00Z",
"senderType": "customer"
},
"unreadCount": 2,
"updatedAt": "2026-02-13T10:30:00Z"
}
]
}Get Single Chat
GET /api/chats/:chatId
Auth: JWT
Response (200):
{
"id": 123,
"customer": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
},
"dentist": {
"id": 5,
"name": "Dr. Smith",
"email": "smith@clinic.com"
},
"status": "active",
"createdAt": "2026-01-15T09:00:00Z",
"updatedAt": "2026-02-13T10:30:00Z"
}Error Responses:
404: Chat not found or user not a participant
Start New Chat
POST /api/chats/start
Auth: JWT (customers only)
Creates a new chat session between the customer and their assigned dentist.
Response (201):
{
"id": 124,
"customer": {
"id": 1,
"name": "John Doe"
},
"dentist": {
"id": 5,
"name": "Dr. Smith"
},
"status": "active",
"createdAt": "2026-02-13T11:00:00Z"
}Error Responses:
400: Chat already exists with this dentist403: Only customers can start chats
Get Messages
GET /api/chats/:chatId/messages
Auth: JWT
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
limit | int | 50 | Number of messages to return |
before | string | - | Cursor for pagination (message ID) |
Response (200):
{
"messages": [
{
"id": 456,
"senderType": "dentist",
"senderId": 5,
"content": "How are your aligners fitting?",
"imageUrls": [],
"createdAt": "2026-02-13T10:00:00Z",
"isRead": true,
"readAt": "2026-02-13T10:05:00Z"
},
{
"id": 457,
"senderType": "customer",
"senderId": 1,
"content": "They're fitting great!",
"imageUrls": [
"https://s3...presigned-url"
],
"createdAt": "2026-02-13T10:30:00Z",
"isRead": false,
"readAt": null
}
],
"hasMore": true,
"nextCursor": "455"
}Send Message
POST /api/chats/:chatId/messages
Auth: JWT
Content-Type: multipart/form-data or application/json
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
content | string | No | Message text (required if no images) |
images | file[] | No | Image files to attach (max 5) |
Example (JSON):
curl -X POST http://localhost:3000/api/chats/123/messages \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{"content": "Hello, I have a question about my treatment."}'Example (with images):
curl -X POST http://localhost:3000/api/chats/123/messages \
-H "Authorization: Bearer <jwt-token>" \
-F "content=Here is my progress photo" \
-F "images=@/path/to/photo.jpg"Response (201):
{
"id": 458,
"senderType": "customer",
"senderId": 1,
"content": "Here is my progress photo",
"imageUrls": [
"https://s3...presigned-url"
],
"createdAt": "2026-02-13T11:00:00Z",
"isRead": false
}Error Responses:
400: Message must have content or images403: User is not a participant in this chat404: Chat not found
Mark Message as Read
POST /api/chats/:chatId/messages/:messageId/read
Auth: JWT
Marks a message as read by the authenticated user.
Response (200):
{
"id": 457,
"isRead": true,
"readAt": "2026-02-13T11:05:00Z"
}Error Responses:
403: Cannot mark own messages as read404: Message not found
Socket.IO Events
Connection
Connect to the Socket.IO server with JWT authentication:
const socket = io('wss://chat-service.example.com', {
auth: {
token: '<jwt-token>'
}
});Upon successful connection, the server automatically joins the user to all their chat session rooms.
Client → Server Events
message:send
Send a new message to a chat session.
socket.emit('message:send', {
sessionId: 123,
content: 'Hello!',
imageUrls: [] // Optional: S3 keys for pre-uploaded images
});typing:start
Indicate the user has started typing.
socket.emit('typing:start', {
sessionId: 123
});typing:stop
Indicate the user has stopped typing.
socket.emit('typing:stop', {
sessionId: 123
});message:read
Mark a message as read.
socket.emit('message:read', {
sessionId: 123,
messageId: 456
});Server → Client Events
connected
Emitted when connection is established and authenticated.
socket.on('connected', (data) => {
console.log('Connected to chat service');
// data: { userId, userType, sessions: [123, 124, ...] }
});message:new
Emitted when a new message is received in any joined session.
socket.on('message:new', (message) => {
// message: {
// id: 458,
// sessionId: 123,
// senderType: 'dentist',
// senderId: 5,
// content: 'Please send a photo',
// imageUrls: [],
// createdAt: '2026-02-13T11:00:00Z'
// }
});typing:indicator
Emitted when another user starts or stops typing.
socket.on('typing:indicator', (data) => {
// data: {
// sessionId: 123,
// userId: 5,
// userType: 'dentist',
// isTyping: true
// }
});user:online
Emitted when the other participant comes online.
socket.on('user:online', (data) => {
// data: { sessionId: 123, userId: 5, userType: 'dentist' }
});user:offline
Emitted when the other participant goes offline.
socket.on('user:offline', (data) => {
// data: { sessionId: 123, userId: 5, userType: 'dentist' }
});message:read_receipt
Emitted when a message has been read by the recipient.
socket.on('message:read_receipt', (data) => {
// data: {
// sessionId: 123,
// messageId: 456,
// readAt: '2026-02-13T11:05:00Z'
// }
});Health Check
GET /health
Auth: None
Returns service health status.
Response (200):
{
"status": "ok",
"timestamp": "2026-02-13T11:00:00Z"
}