Parent: Chat Service


Endpoints Summary

MethodPathAuthDescription
GET/api/chatsJWTGet all chats for authenticated user
GET/api/chats/:chatIdJWTGet single chat details
POST/api/chats/startJWTStart new chat (customers only)
GET/api/chats/:chatId/messagesJWTGet messages (paginated)
POST/api/chats/:chatId/messagesJWTSend message (text + images)
POST/api/chats/:chatId/messages/:messageId/readJWTMark message as read
GET/healthNoneHealth 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 dentist
  • 403: Only customers can start chats

Get Messages

GET /api/chats/:chatId/messages

Auth: JWT

Query Parameters:

ParamTypeDefaultDescription
limitint50Number of messages to return
beforestring-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:

FieldTypeRequiredDescription
contentstringNoMessage text (required if no images)
imagesfile[]NoImage 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 images
  • 403: User is not a participant in this chat
  • 404: 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 read
  • 404: 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"
}