Skip to main content

Messages

Each civic issue has a threaded chat discussion. Messages are delivered in real-time via Socket.IO and are also accessible through the REST API for initial page loads and SSR scenarios.


GET /api/issues/:id/messages​

Retrieve the message thread for an issue, ordered by creation time ascending. No authentication required.

Query parameters:

ParameterTypeDefaultDescription
limitnumber50Number of messages per page (1–100)
lastKeystringβ€”Cursor from previous page's nextKey

Request:

GET /api/issues/iss_01HN5K.../messages?limit=20

Response β€” 200 OK:

{
"success": true,
"data": {
"count": 7,
"items": [
{
"messageId": "msg_01HP...",
"issueId": "iss_01HN5K...",
"content": "I've been complaining about this for two months. BBMP keeps closing the complaint without fixing it.",
"author": {
"userId": "usr_01HN...",
"displayName": "Priya S.",
"avatarUrl": "https://lh3.googleusercontent.com/...",
"role": "citizen"
},
"reactions": {
"πŸ‘": 12,
"😑": 5,
"πŸ™": 3
},
"myReaction": null,
"createdAt": "2025-01-15T09:10:00.000Z",
"editedAt": null
},
{
"messageId": "msg_01HQ...",
"issueId": "iss_01HN5K...",
"content": "Escalated to the ward councillor. Reference number: BBMP/2025/0987.",
"author": {
"userId": "off_01HN...",
"displayName": "BBMP Ward Officer",
"avatarUrl": null,
"role": "official"
},
"reactions": {
"πŸ‘": 28,
"❀️": 7
},
"myReaction": "πŸ‘",
"createdAt": "2025-01-15T11:30:00.000Z",
"editedAt": null
}
],
"nextKey": null,
"_links": {
"self": "/api/issues/iss_01HN5K.../messages?limit=20",
"next": null
}
}
}

The myReaction field is populated only when the request includes a valid Authorization header. It reflects which emoji the authenticated user has reacted with on that message, or null if they have not reacted.


POST /api/issues/:id/messages​

Post a new message to an issue thread. Authentication required.

Request:

POST /api/issues/iss_01HN5K.../messages
Authorization: Bearer <accessToken>
Content-Type: application/json

{
"content": "The BBMP complaint portal shows this as 'resolved' but the light is still out. Attaching screenshot."
}

Validation rules:

  • content: required, 1–1000 characters, will be XSS-sanitized

Response β€” 201 Created:

{
"success": true,
"data": {
"messageId": "msg_01HR...",
"issueId": "iss_01HN5K...",
"content": "The BBMP complaint portal shows this as 'resolved' but the light is still out. Attaching screenshot.",
"author": {
"userId": "usr_01HS...",
"displayName": "Rahul M.",
"avatarUrl": "https://lh3.googleusercontent.com/...",
"role": "citizen"
},
"reactions": {},
"createdAt": "2025-01-16T08:15:00.000Z"
}
}

Immediately after the REST response, the server emits a message:new event to all clients in room:issue:iss_01HN5K... via Socket.IO. See Real-Time Architecture for the event payload.


DELETE /api/issues/:id/messages/:msgId​

Delete a message. Authentication required. The requesting user must be the message author or an admin.

Request:

DELETE /api/issues/iss_01HN5K.../messages/msg_01HR...
Authorization: Bearer <accessToken>

Response β€” 200 OK:

{
"success": true,
"data": {
"messageId": "msg_01HR...",
"deleted": true
}
}

Deleted messages are soft-deleted β€” the record is retained in DynamoDB with deleted: true and the content replaced with [message deleted]. This preserves conversation threading while removing the content.

Response β€” 403 (not the author or admin):

{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "You can only delete your own messages"
}
}

POST /api/issues/:id/messages/:msgId/react​

Add or toggle a reaction emoji on a message. Authentication required. Each user can have one active reaction per message. Sending the same emoji again removes the reaction (toggle). Sending a different emoji replaces the existing reaction.

Supported reactions: πŸ‘ ❀️ 😑 😒 πŸ™ 🚨

Request:

POST /api/issues/iss_01HN5K.../messages/msg_01HR.../react
Authorization: Bearer <accessToken>
Content-Type: application/json

{
"emoji": "πŸ™"
}

Response β€” 200 OK:

{
"success": true,
"data": {
"messageId": "msg_01HR...",
"reactions": {
"πŸ‘": 12,
"😑": 5,
"πŸ™": 4
},
"myReaction": "πŸ™"
}
}

Response β€” 200 OK (toggled off β€” same emoji sent again):

{
"success": true,
"data": {
"messageId": "msg_01HR...",
"reactions": {
"πŸ‘": 12,
"😑": 5,
"πŸ™": 3
},
"myReaction": null
}
}

After the REST response, the server emits a message:reaction event to all clients in the issue room with the updated reaction counts. See Real-Time Architecture.

Response β€” 400 (unsupported emoji):

{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "emoji must be one of: πŸ‘ ❀️ 😑 😒 πŸ™ 🚨"
}
}

Real-Time Delivery​

Messages and reactions are delivered to connected clients in real-time via Socket.IO without polling. The REST endpoints are used for:

  1. Initial page load β€” fetch existing messages when a user first opens an issue
  2. Offline recovery β€” catch up on messages missed during a disconnection

Once the page has loaded and the Socket.IO connection is established, new messages arrive via the message:new event and new reactions via message:reaction. The client appends/updates these in local state without needing to re-fetch.

See Real-Time Architecture for event schemas and room management.