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:
- You start the interview via
POST /api/v1/interviews/start. The response includes aninitial_message— this is the first thing the interviewer says to the respondent. - The respondent replies. Your application collects the respondent's input (text or voice) and sends it to Koji.
- 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.
- 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
| Header | Value | Required |
|---|---|---|
Authorization | Bearer your_api_key | Yes |
X-Session-Token | session_token_from_start | Yes |
Content-Type | application/json | Yes |
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 Type | Description |
|---|---|
transcript | Real-time transcription of the respondent's speech |
interviewer_start | The interviewer has begun speaking |
interviewer_end | The interviewer has finished speaking |
interview_status | Status update (active, completing, completed) |
error | An 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
| Header | Value | Required |
|---|---|---|
Authorization | Bearer your_api_key | Yes |
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:
- Display the initial_message from the start response as the first chat bubble.
- Collect respondent input via a text field or voice recording interface.
- Send the message to the
/messageendpoint. - Stream the response — process SSE chunks to display the interviewer's reply progressively.
- Handle structured questions — if the stream includes a structured question event, render the appropriate widget.
- 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 Code | Error | Meaning |
|---|---|---|
| 400 | invalid_message | Message content is empty or malformed |
| 401 | unauthorized | Invalid API key or session token |
| 403 | forbidden | Key lacks interview:chat permission |
| 404 | not_found | Interview does not exist |
| 409 | conflict | Interview is already completed |
| 429 | rate_limited | Too many messages in a short period |
Implement retry logic with exponential backoff for transient errors like 429 and 5xx responses.
Next Steps
Related Articles
Starting Interviews via API
Use the POST /start endpoint to programmatically launch interviews from your application.
Completing Interviews via API
Use the POST /complete endpoint to finish an interview session and trigger automatic analysis.
Structured Questions in AI Interviews
Mix quantitative data collection — scales, ratings, multiple choice, ranking — with AI-powered conversational follow-up in a single interview.
Send Research Insights to Slack: Real-Time Customer Interview Notifications via Webhooks
Pipe customer interview insights from Koji into your Slack workspace in real time. Use Koji webhooks to notify a #research channel the moment an interview completes, post quote highlights to #product-feedback, or alert #cs-alerts when a churn signal is detected. Step-by-step setup with a working Slack incoming webhook recipe.
Connect Koji to Zapier: Automate Customer Research Workflows in Minutes
Route every completed AI customer interview from Koji into 6,000+ Zapier apps — including Notion, Linear, Salesforce, Airtable, and Gmail. A step-by-step integration guide.
API Authentication
Learn how to authenticate with the Koji API using API keys and Bearer tokens.