Overview
The Chat API lets you embed a TalkifAI text agent as a chat interface in any website or application. It uses standard REST + Server-Sent Events (SSE) streaming — no WebRTC or microphone required.
Text agents only. This API requires agents with text architecture. Voice agents (Pipeline/Realtime) cannot be used here.
Base URL:
https://api.talkifai.dev/v1/chat
Authentication — two tokens used at different stages:
| Stage | Header | Value |
|---|
| Create session | X-API-Key | Your tk_live_... API key (server-side only) |
| Send messages | Authorization | Bearer {session_token} (JWT from create response) |
Endpoints
| Method | Path | Description |
|---|
POST | /v1/chat/sessions | Create a new chat session |
POST | /v1/chat/sessions/{id}/messages | Send a message (SSE stream) |
GET | /v1/chat/sessions/{id}/messages | Get message history |
POST | /v1/chat/sessions/{id}/end | End the session |
Create Session
POST /v1/chat/sessions
X-API-Key: tk_live_your_key_here
Content-Type: application/json
Request body:
{
"agent_id": "5b710eca-ee67-4c3a-aeb6-8b541f451b40"
}
| Field | Required | Description |
|---|
agent_id | Yes | ID of the text agent to use |
Response:
{
"conversation_id": "chat_5b710eca_user123_1705312800000",
"session_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"agent_name": "Support Agent",
"greeting": "Hello! How can I help you today?",
"agent_model": "gpt-4o-mini"
}
| Field | Description |
|---|
conversation_id | Unique session ID — use in all subsequent calls |
session_token | JWT for authenticating subsequent messages (24h expiry) |
agent_name | Name of the agent |
greeting | Agent’s opening message — display immediately in chat UI. null if agent uses userFirst greeting mode |
agent_model | LLM model the agent is using |
Make this call from your backend server only. Never expose your API key in frontend JavaScript.
Send Message
POST /v1/chat/sessions/{conversation_id}/messages
Authorization: Bearer {session_token}
Content-Type: application/json
Request body:
{
"message": "What are your business hours?"
}
| Field | Required | Description |
|---|
message | Yes | User’s message text (max 10,000 characters) |
Response: Server-Sent Events (SSE) stream (Content-Type: text/event-stream)
Each SSE event has a named event: field followed by a data: line, separated by a blank line:
event: stream_start
data: {"agent_name": "Support Agent"}
event: chunk
data: {"delta": "Our business "}
event: chunk
data: {"delta": "hours are 9am–6pm EST."}
event: stream_end
data: {"usage": {"input_tokens": 12, "output_tokens": 18}, "end_session": false}
SSE Event Types
| Event | Data fields | Description |
|---|
stream_start | { "agent_name": "..." } | Agent started responding — show loading indicator |
chunk | { "delta": "..." } | Incremental text — append to chat bubble |
tool_call | { "name": "...", "status": "executing" } | Agent invoked a tool — optionally show status to user |
tool_result | { "name": "...", "status": "completed" } | Tool finished — agent continues streaming |
handoff | { "from_agent": "...", "to_agent": "..." } | Multi-agent handoff — a subagent is now responding |
stream_end | { "usage": {"input_tokens": N, "output_tokens": N}, "end_session": bool } | Response complete — clear loading. If end_session: true, call /end immediately |
error | { "error": "..." } | Error mid-stream — clear loading state and show error message to user |
Parsing the Stream
SSE events are separated by blank lines (\n\n). Each event block contains event: and data: lines. Parse them together — do not rely on the data: line alone to determine event type.
const response = await fetch(
`https://api.talkifai.dev/v1/chat/sessions/${conversationId}/messages`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ message: userInput })
}
);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullText = '';
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// SSE events are separated by blank lines
const blocks = buffer.split('\n\n');
buffer = blocks.pop(); // keep any incomplete trailing block
for (const block of blocks) {
let eventType = '';
let dataStr = '';
for (const line of block.split('\n')) {
if (line.startsWith('event: ')) eventType = line.slice(7).trim();
else if (line.startsWith('data: ')) dataStr = line.slice(6);
}
if (!dataStr) continue;
const data = JSON.parse(dataStr);
if (eventType === 'stream_start') {
// Show loading indicator
} else if (eventType === 'chunk') {
fullText += data.delta;
// Update chat bubble with streaming text
} else if (eventType === 'tool_call') {
// Optionally show "Looking that up..." indicator
} else if (eventType === 'tool_result') {
// Tool finished — agent continues
} else if (eventType === 'handoff') {
// { from_agent, to_agent } — a subagent took over
} else if (eventType === 'stream_end') {
setIsLoading(false); // clear loading state
if (data.end_session) {
// Agent called end_chat — close the session immediately
endSession();
}
} else if (eventType === 'error') {
setIsLoading(false); // clear loading — don't leave UI hanging
showError(data.error || 'Something went wrong. Please try again.');
}
}
}
Get Message History
GET /v1/chat/sessions/{conversation_id}/messages
Authorization: Bearer {session_token}
Query parameters:
| Param | Default | Description |
|---|
limit | 50 | Number of messages to return |
offset | 0 | Pagination offset |
Response:
{
"messages": [
{
"role": "assistant",
"content": "Hello! How can I help you today?",
"timestamp": "2024-01-15T10:00:00+00:00",
"token_count": null
},
{
"role": "user",
"content": "What are your business hours?",
"timestamp": "2024-01-15T10:00:08+00:00",
"token_count": null
},
{
"role": "assistant",
"content": "Our business hours are 9am–6pm EST, Monday through Friday.",
"timestamp": "2024-01-15T10:00:09+00:00",
"token_count": 18
}
],
"count": 3
}
End Session
Always end sessions explicitly to trigger memory ingestion, post-call analysis, and webhooks.
POST /v1/chat/sessions/{conversation_id}/end
Authorization: Bearer {session_token}
What happens:
- Billing session closed and credits consumed
- Memory ingested into Graphiti (for returning user context)
- Post-call analysis triggered (if configured on agent)
- Webhook fired (if agent has webhook configured)
Response:
{
"success": true,
"message": "Session ended successfully"
}
Sessions automatically expire after 30 minutes of inactivity. Always call /end explicitly to ensure memory ingestion and webhooks fire correctly.
Error Codes
| Status | Code | Cause | Fix |
|---|
400 | not_text_agent | Agent is Pipeline or Realtime (not Text) | Create a Text architecture agent |
400 | message_too_long | Message exceeds 10,000 characters | Trim the message |
401 | invalid_api_key | API key missing, invalid, or expired | Check X-API-Key header |
401 | invalid_token | Session JWT expired or invalid | Create a new session |
402 | insufficient_credits | Organization has insufficient credits | Top up credits in billing settings |
403 | conversation_mismatch | conversation_id in URL does not match the session token | Use the correct conversation_id returned from session creation |
404 | session_not_found | Session expired (idle >30 min) or wrong ID | Create a new session |
500 | — | Unexpected server error | Retry or contact support |
503 | — | Billing service temporarily unavailable | Retry after a short delay |
Session Lifecycle
POST /sessions → session_token + conversation_id
↓
POST /messages → SSE stream (repeatable)
↓
POST /end → billing cleanup + memory + webhooks
Sessions expire automatically after 30 minutes of inactivity, even without calling /end. However, calling /end is required to trigger billing finalization, memory ingestion, and post-call analysis.