Internal Documentation internal
TalkIDE internal documentation

Send a message in an existing ACTIVE conversation. Only the project owner can send messages. The conversation must be ACTIVE.

  • Sending a message saves a USER message to the database and immediately returns it in the response.
  • The backend then spawns a Claude Code CLI process in the background to generate a PM response.
  • The PM response is streamed to the frontend via a separate SSE endpoint (see SSE stream section below).
  • The response returns only the USER message — the PM response arrives asynchronously via SSE.
  • The conversation’s updatedAt is set to the timestamp of the latest message after each send.
  • If the conversation is CLOSED, sending a message is rejected.
  • If a Claude Code CLI process is already running for this conversation, sending another message is rejected with 409 CONFLICT_PROCESSING.
sequenceDiagram
    actor User

    User->>+FE: types message and submits

    FE->>FE: validate form
    alt form is invalid
        FE-->>User: show error messages <br> disable submit button
    end

    FE->>+BE: POST /api/v1/projects/{projectId}/conversations/{conversationId}/messages <br> Authorization: Bearer {accessToken} <br> SendMessageRequest

    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->>BE: check conversation status is ACTIVE
    alt conversation is CLOSED
        BE-->>FE: 409 Conflict <br> ErrorResponse
    end

    BE->>BE: check no CLI process is already running for this conversation
    alt CLI is already processing
        BE-->>FE: 409 Conflict <br> ErrorResponse
    end

    BE->>BE: validate request body
    alt request is invalid
        BE-->>FE: 400 Bad Request <br> ErrorResponse
    end

    BE->>DB: insert USER message

    BE->>DB: update conversation updatedAt

    BE->>-FE: 201 Created <br> NewMessagesResponse (USER message only)

    FE->>FE: append USER message to chat view
    FE->>FE: show typing indicator

    FE->>+BE: GET /api/v1/projects/{projectId}/conversations/{conversationId}/stream <br> Authorization: Bearer {accessToken}

    BE->>BE: spawn Claude Code CLI process in background

    loop CLI outputs NDJSON lines
        BE->>BE: parse NDJSON line
        alt line is PM message (type=assistant, parent_tool_use_id=null)
            BE->>DB: insert PM message
            BE-->>FE: SSE event: message <br> MessageDto
        end
    end

    BE-->>FE: SSE event: done
    BE->>-FE: close SSE stream

    FE->>-User: hide typing indicator, PM messages visible in chat

POST /api/v1/projects/{projectId}/conversations/{conversationId}/messages SendMessageRequest:

{
  "content": "Can you also add a phone number field to the contact form?"
}

201 Created NewMessagesResponse (USER message only):

{
  "data": {
    "messages": [
      {
        "id": 25,
        "role": "USER",
        "content": "Can you also add a phone number field to the contact form?",
        "createdAt": "2026-04-29T12:15:00Z"
      }
    ]
  }
}

400 Bad Request (validation) ErrorResponse:

{
  "status": 400,
  "code": "VALIDATION_ERROR",
  "message": "Validation failed",
  "errors": [
    { "field": "content", "message": "must not be blank" }
  ]
}

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"
}

409 Conflict (conversation is CLOSED) ErrorResponse:

{
  "status": 409,
  "code": "CONFLICT_CONVERSATION",
  "message": "Cannot send a message to a CLOSED conversation"
}

409 Conflict (another message is already being processed) ErrorResponse:

{
  "status": 409,
  "code": "CONFLICT_PROCESSING",
  "message": "A message is already being processed in this conversation"
}

SSE Stream

GET /api/v1/projects/{projectId}/conversations/{conversationId}/stream

Authorization: Bearer token. The same project/tenant/conversation validations apply as for the POST endpoint. The response is a text/event-stream (SSE). The stream closes automatically when the CLI process finishes.

SSE event: message — a PM response message was saved to the database:

{"id": 26, "role": "PM", "content": "Great idea! I understand your request...", "createdAt": "2026-04-29T12:15:05Z"}

SSE event: done — the CLI process finished, the stream closes:

{}

SSE event: error — the CLI process failed:

{"message": "AI processing failed"}

Claude Code CLI Integration

The backend spawns a Claude Code CLI process for each user message. The command is:

claude -p "<user message>" --plugin-dir <plugin-dir> --agent plugin:talkide-pm --output-format stream-json --verbose --session-id <conversation-session-uuid>

The CLI is invoked from the project’s output directory (<output-dir>/<project-slug>/). The project slug is extracted from the project’s url field (the part before .talkide.app).

The CLI output is NDJSON (one JSON object per line). PM messages are identified by parent_tool_use_id: null and type: "assistant". Sub-agent messages (backend developer, reviewer, etc.) have a non-null parent_tool_use_id and are NOT forwarded to the user.

Session continuity: each conversation has a session_id (UUID). The first message generates a new UUID. Subsequent messages reuse the same session_id for multi-turn conversation with the AI. The session_id is stored on the CONVERSATION entity.

Frontend

Validations

FieldConstraintsSizePatternNote
contentnot_blank1 - 5000

Backend

Validations

FieldConstraintsSizePatternNote
projectIdnot_null, positivePath variable; must reference an existing project
conversationIdnot_null, positivePath variable; must reference a conversation belonging to the project
contentnot_blank1 - 5000

Test Cases

GIVENWHENTHEN
authenticated user, ACTIVE conversation, valid content, no CLI runningPOST /conversations/{conversationId}/messages is called201 Created with USER message returned; CLI process spawned in background
authenticated user, ACTIVE conversation, CLI already runningPOST /conversations/{conversationId}/messages is called409 CONFLICT_PROCESSING error response is returned
authenticated user, CLOSED conversationPOST /conversations/{conversationId}/messages is called409 CONFLICT_CONVERSATION error response is returned
authenticated user, conversation does not existPOST /conversations/{conversationId}/messages is called404 NOT_FOUND_CONVERSATION error response is returned
authenticated user, conversation belongs to a different projectPOST /conversations/{conversationId}/messages is called404 NOT_FOUND_CONVERSATION error response is returned
authenticated user, project does not existPOST /conversations/{conversationId}/messages is called404 NOT_FOUND_PROJECT error response is returned
authenticated user, project belongs to a different tenantPOST /conversations/{conversationId}/messages is called403 FORBIDDEN error response is returned
content is blankPOST /conversations/{conversationId}/messages is called400 VALIDATION_ERROR error response is returned
content longer than 5000 charactersPOST /conversations/{conversationId}/messages is called400 VALIDATION_ERROR error response is returned
no Authorization headerPOST /conversations/{conversationId}/messages is called401 AUTHENTICATION_FAILED error response is returned
valid requestPOST /conversations/{conversationId}/messages is calledconversation updatedAt is refreshed to current timestamp
CLI outputs PM message (type=assistant, parent_tool_use_id=null)SSE stream is openSSE event “message” with PM MessageDto is sent; PM message is saved to DB
CLI outputs sub-agent message (parent_tool_use_id not null)SSE stream is openmessage is ignored, not forwarded via SSE
CLI process finishes successfullySSE stream is openSSE event “done” is sent and stream closes
CLI process failsSSE stream is openSSE event “error” is sent and stream closes
first message in conversationPOST /conversations/{conversationId}/messages is callednew session_id UUID is generated and stored on the conversation
subsequent message in conversationPOST /conversations/{conversationId}/messages is calledexisting session_id is reused for CLI —session-id argument

Was this page helpful?

Thanks for the feedback.