New

Now in Claude, ChatGPT, Cursor & more with our MCP server

Back to docs
API Reference

Sending Messages via API

Understand how messages flow between your application and Koji during an API-started interview.

Sending Messages via API

Once you have started an interview, your application needs to handle the back-and-forth conversation between the respondent and Koji's interviewer. This article explains how messages flow in API-started interviews and how to integrate the conversational experience into your own UI.


How Message Flow Works

An API-started interview follows a structured conversational pattern:

  1. You start the interview via POST /api/v1/interviews/start. The response includes an initial_message — this is the first thing the interviewer says to the respondent.
  2. The respondent replies. Your application collects the respondent's input (text or voice) and sends it to Koji.
  3. Koji processes the response and streams the next interviewer message back via Server-Sent Events (SSE), including follow-up questions that adapt to what the respondent said.
  4. The cycle repeats until the interview reaches a natural conclusion or you explicitly complete it.

This exchange happens through the interview session, with messages flowing through Koji's conversation engine.


Text-Based Message Flow

For text-mode interviews, messages are exchanged through the message endpoint:

POST https://koji.so/api/v1/interviews/:interview_id/message

Important: The endpoint path uses singular /message, not /messages.

Headers

HeaderValueRequired
AuthorizationBearer your_api_keyYes
X-Session-Tokensession_token_from_startYes
Content-Typeapplication/jsonYes

The API key must have the interview:chat permission to call this endpoint.

Request Body

{
  "content": "The respondent's message text goes here."
}

Streaming Response (SSE)

The message endpoint streams the interviewer's response using Server-Sent Events. Your client should handle the stream as it arrives:

const response = await fetch(
  `https://koji.so/api/v1/interviews/${interviewId}/message`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'X-Session-Token': sessionToken,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ content: respondentMessage })
  }
);

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  
  const chunk = decoder.decode(value);
  // Process SSE chunks — each contains a portion
  // of the interviewer's response
  handleStreamChunk(chunk);
}

Structured Response Handling

When the interview includes structured questions, the SSE stream may include structured question events. These indicate that the interviewer is presenting a widget (such as a scale slider, multiple choice selector, or ranking interface) to the respondent. Your UI should render the appropriate input widget and send the structured response back.


Voice-Based Message Flow

Voice interviews use a different mechanism. When you start a voice interview, the response includes voice_credentials with a WebSocket URL and authentication token.

Establishing the Voice Connection

const ws = new WebSocket(voice_credentials.server_url);

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: 'auth',
    token: voice_credentials.token
  }));
};

Once connected and authenticated, audio streams bidirectionally over the WebSocket. The voice service handles speech-to-text, processes the response through Koji's conversation engine, and streams synthesized speech back.

Voice Events

The WebSocket sends JSON events alongside audio data:

Event TypeDescription
transcriptReal-time transcription of the respondent's speech
interviewer_startThe interviewer has begun speaking
interviewer_endThe interviewer has finished speaking
interview_statusStatus update (active, completing, completed)
errorAn error occurred in the voice session

Handle these events to update your UI — for example, showing a transcript as the respondent speaks or displaying a visual indicator when the interviewer is responding.


Retrieving the Conversation

At any point during or after the interview, you can retrieve the full conversation:

GET https://koji.so/api/v1/interviews/:interview_id

Headers

HeaderValueRequired
AuthorizationBearer your_api_keyYes

The API key must have the interview:read permission. The response includes the full transcript, each message with its sender, timestamp, and content. After the interview is completed, it also includes analysis results, quality scores, and structured answers. See Completing Interviews via API for more on the analysis payload.


Building Your Own Chat UI

When integrating the message flow into your application, here is a recommended approach:

  1. Display the initial_message from the start response as the first chat bubble.
  2. Collect respondent input via a text field or voice recording interface.
  3. Send the message to the /message endpoint.
  4. Stream the response — process SSE chunks to display the interviewer's reply progressively.
  5. Handle structured questions — if the stream includes a structured question event, render the appropriate widget.
  6. Check interview status — if it indicates the interview is winding down, prepare to call the complete endpoint.

Keep the experience conversational. Avoid overwhelming respondents with too much UI chrome. The interview should feel like a natural conversation, not a form.


Message Content Guidelines

When sending respondent messages to the API:

  • Send the raw text. Do not pre-process, summarize, or modify what the respondent typed.
  • Preserve formatting. If the respondent uses line breaks, keep them.
  • Do not inject instructions. Sending hidden prompts or instructions alongside the respondent's message violates the terms of service and produces unreliable results.
  • Handle empty messages. Validate on your end that the message is not empty before sending.

Conversation Length

Koji's interview engine manages conversation length based on your research brief configuration. The interviewer naturally wraps up the conversation when it has covered the topics defined in the brief. You can also end the interview at any time by calling the complete endpoint.

Typical interviews last 5 to 15 minutes for text and 8 to 20 minutes for voice, but this varies based on the research brief complexity and respondent engagement.


Error Handling

Status CodeErrorMeaning
400invalid_messageMessage content is empty or malformed
401unauthorizedInvalid API key or session token
403forbiddenKey lacks interview:chat permission
404not_foundInterview does not exist
409conflictInterview is already completed
429rate_limitedToo many messages in a short period

Implement retry logic with exponential backoff for transient errors like 429 and 5xx responses.


Next Steps