Get a conversation with its full message history. Messages are paginated and returned oldest first for correct chat display. Only the project owner can access this endpoint.
- Messages are sorted by
createdAtASC (oldest first) so the UI can render them as a chat thread from top to bottom. - Pagination is applied to messages, not conversations.
- The
messageCountfield on the conversation summary reflects the total number of messages (across all pages). SYSTEMrole messages are included in the message list and can be used by the frontend as progress indicators.
sequenceDiagram
actor User
User->>+FE: opens conversation view
FE->>+BE: GET /api/v1/projects/{projectId}/conversations/{conversationId} <br> Authorization: Bearer {accessToken}
BE->>BE: validate access token
alt token missing or invalid
BE-->>FE: 401 Unauthorized <br> ErrorResponse
end
BE->>DB: load project by projectId
alt project not found
BE-->>FE: 404 Not Found <br> ErrorResponse
end
BE->>BE: check project belongs to user's tenant
alt project does not belong to tenant
BE-->>FE: 403 Forbidden <br> ErrorResponse
end
BE->>DB: load conversation by conversationId (scoped to projectId)
alt conversation not found
BE-->>FE: 404 Not Found <br> ErrorResponse
end
BE->>DB: query messages for conversation (paginated, oldest first)
BE->>-FE: 200 OK <br> ConversationDetailResponse
FE->>-User: render conversation and message history
GET /api/v1/projects/{projectId}/conversations/{conversationId}
Query parameters (for message pagination):
page(optional, default 0) — zero-based page numbersize(optional, default 50) — page size
200 OK ConversationDetailResponse:
{
"data": {
"id": 8,
"title": "I want to add a contact form to the homepage",
"status": "ACTIVE",
"messageCount": 4,
"createdAt": "2026-04-29T12:00:00Z",
"updatedAt": "2026-04-29T12:15:01Z",
"messages": {
"content": [
{
"id": 21,
"role": "USER",
"content": "I want to add a contact form to the homepage with name, email, and message fields.",
"createdAt": "2026-04-29T12:00:00Z"
},
{
"id": 22,
"role": "PM",
"content": "Great idea! I understand your request. Let me analyze the project structure and plan the implementation. I'll outline the necessary changes shortly.",
"createdAt": "2026-04-29T12:00:01Z"
},
{
"id": 25,
"role": "USER",
"content": "Can you also add a phone number field to the contact form?",
"createdAt": "2026-04-29T12:15:00Z"
},
{
"id": 26,
"role": "PM",
"content": "Understood! I've noted your request and I'm already thinking about how to approach this. Give me a moment to analyze the current state of the project.",
"createdAt": "2026-04-29T12:15:01Z"
}
],
"page": 0,
"size": 50,
"totalElements": 4,
"totalPages": 1
}
}
}
401 Unauthorized (missing or invalid access token) ErrorResponse:
{
"status": 401,
"code": "AUTHENTICATION_FAILED",
"message": "Access token is missing or invalid"
}
403 Forbidden (project does not belong to user’s tenant) ErrorResponse:
{
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have access to this project"
}
404 Not Found (project not found) ErrorResponse:
{
"status": 404,
"code": "NOT_FOUND_PROJECT",
"message": "Project not found"
}
404 Not Found (conversation not found or does not belong to project) ErrorResponse:
{
"status": 404,
"code": "NOT_FOUND_CONVERSATION",
"message": "Conversation not found"
}
Frontend
Validations
| Field | Constraints | Size | Pattern | Note |
|---|---|---|---|---|
| page | (optional) | — | — | Zero-based; defaults to 0 |
| size | (optional) | — | — | Defaults to 50 |
Backend
Validations
| Field | Constraints | Size | Pattern | Note |
|---|---|---|---|---|
| projectId | not_null, positive | — | — | Path variable; must reference an existing project |
| conversationId | not_null, positive | — | — | Path variable; must reference a conversation belonging to the project |
Test Cases
| GIVEN | WHEN | THEN |
|---|---|---|
| authenticated user, valid conversation with messages | GET /conversations/{conversationId} is called | 200 OK with conversation metadata and paginated messages oldest first |
| authenticated user, conversation with no messages | GET /conversations/{conversationId} is called | 200 OK with empty messages content list |
| authenticated user, conversation does not exist | GET /conversations/{conversationId} is called | 404 NOT_FOUND_CONVERSATION error response is returned |
| authenticated user, conversation belongs to a different project | GET /conversations/{conversationId} is called | 404 NOT_FOUND_CONVERSATION error response is returned |
| authenticated user, project does not exist | GET /conversations/{conversationId} is called | 404 NOT_FOUND_PROJECT error response is returned |
| authenticated user, project belongs to a different tenant | GET /conversations/{conversationId} is called | 403 FORBIDDEN error response is returned |
| no Authorization header | GET /conversations/{conversationId} is called | 401 AUTHENTICATION_FAILED error response is returned |
| conversation with many messages, page=1, size=2 | GET /conversations/{conversationId}?page=1&size=2 is called | 200 OK with correct page of messages |
Was this page helpful?
Thanks for the feedback.