Appearance
Logging Conversations
Log conversations to track performance and generate insights.
Why Log Conversations?
Logging conversations enables:
- Performance tracking - See how your agents perform with real users
- Insights generation - AI analyzes patterns and issues
- Optimization signal - Real data guides improvements
Structured Format
For best results, use the structured message format instead of raw text transcripts.
Via SDK (Recommended)
The SDK makes logging easy:
typescript
import { Converra } from 'converra';
import OpenAI from 'openai';
const converra = new Converra({ apiKey: process.env.CONVERRA_API_KEY });
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// 1. Get your agent
const agent = await converra.agents.get('agent_123');
// 2. Run your AI conversation
const messages = [
{ role: 'system', content: agent.content },
{ role: 'user', content: userMessage }
];
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages
});
const assistantMessage = response.choices[0].message.content;
// 3. Log the conversation
await converra.conversations.create({
agentId: 'agent_123',
content: `User: ${userMessage}\nAssistant: ${assistantMessage}`,
status: 'completed',
llmModel: 'gpt-4o'
});Multi-Turn Conversations
Two patterns. Pick based on when you have the data.
Option A — Batch (send the full transcript once)
If you already have the finished conversation in hand (webhook, async job, post-call processing), send it in a single call:
typescript
await converra.conversations.create({
agentId: 'agent_123',
content: [
'User: Hi, I need help with my order',
"Assistant: I'd be happy to help! What's your order number?",
'User: #12345',
'Assistant: Found it! Your order ships tomorrow.',
].join('\n'),
status: 'completed', // triggers analysis
});Option B — Incremental (send turns as they happen)
If you want the conversation visible in Converra mid-flight — long chats, voice, streaming agents — send each turn as it happens. Create once, append as you go, flip status to "completed" on the final call.
typescript
// SDK: one method, routes by whether you pass a conversationId
import { Converra } from 'converra';
const converra = new Converra({ apiKey: process.env.CONVERRA_API_KEY! });
// First turn — creates the conversation, returns an id
const { conversationId } = await converra.send({
agent: 'Support Bot',
messages: [{ role: 'user', content: 'Hi, I need help with my order' }],
status: 'active',
});
// Each subsequent turn — pass the same conversationId
await converra.send({
agent: 'Support Bot',
conversationId,
messages: [{ role: 'assistant', content: "I'd be happy to help! What's your order number?" }],
status: 'active',
});
await converra.send({
agent: 'Support Bot',
conversationId,
messages: [{ role: 'user', content: '#12345' }],
status: 'active',
});
// Final turn — set status to 'completed' to trigger analysis
await converra.send({
agent: 'Support Bot',
conversationId,
messages: [{ role: 'assistant', content: 'Found it! Your order ships tomorrow.' }],
status: 'completed',
});Three rules:
- No
conversationIdyet? Call creates a new conversation. Have one? Call appends to it. - Keep the
conversationIdfrom the first response — it's the only state you need to carry. - Send
status: "completed"exactly once, on the final call. Without it the conversation staysactiveand is never analyzed.
Raw HTTP (no SDK)
Same two patterns against the REST API:
bash
# First turn — create
curl -X POST https://converra.ai/api/v1/conversations \
-H "Authorization: Bearer $CONVERRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agentId": "agent_123",
"content": "User: Hi",
"status": "active",
"messages": [{"role": "user", "content": "Hi"}]
}'
# Response includes { "id": "...", "_links": { "appendMessages": { "href": "/api/v1/conversations/.../messages", "method": "POST", ... } } }
# Each subsequent turn — append
curl -X POST https://converra.ai/api/v1/conversations/{id}/messages \
-H "Authorization: Bearer $CONVERRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "assistant", "content": "Hello!"}],
"status": "active"
}'
# Final turn — flip status
curl -X POST https://converra.ai/api/v1/conversations/{id}/messages \
-H "Authorization: Bearer $CONVERRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "assistant", "content": "Goodbye!"}],
"status": "completed"
}'Via API
bash
curl -X POST https://converra.ai/api/v1/conversations \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agentId": "agent_123",
"content": "User: Hello\nAssistant: Hi there! How can I help?",
"status": "completed",
"llmModel": "gpt-4o"
}'Via MCP
Log this conversation for my support agent:
User: How do I cancel my subscription?
Assistant: I can help you cancel. Go to Settings > Subscription > Cancel.
User: Thanks!
Assistant: You're welcome! Your subscription will end on the billing date.Conversation Format
Converra accepts two formats for conversation content.
Structured Format (Recommended)
Use a JSON messages array for best parsing and analysis:
typescript
await converra.conversations.create({
agentId: 'agent_123',
content: JSON.stringify({
messages: [
{ role: 'user', content: 'I need help with my order' },
{ role: 'assistant', content: 'I\'d be happy to help! What\'s your order number?' },
{ role: 'user', content: '#12345' },
{ role: 'assistant', content: 'Found it! Your order ships tomorrow.' }
]
}),
status: 'completed',
llmModel: 'gpt-4o'
});Benefits:
- Precise message boundaries
- Role attribution is unambiguous
- Metadata can be included per message
- Direct compatibility with OpenAI/Anthropic formats
Extended format with metadata:
json
{
"messages": [
{
"role": "user",
"content": "I need help with my order",
"timestamp": "2025-01-20T14:30:00Z"
},
{
"role": "assistant",
"content": "I'd be happy to help! What's your order number?",
"timestamp": "2025-01-20T14:30:02Z",
"model": "gpt-4o",
"tokens": 15
}
],
"metadata": {
"userId": "user_123",
"sessionId": "sess_456",
"channel": "web",
"language": "en"
}
}Text Format
For simpler integrations, use alternating labeled messages:
User: First user message
Assistant: First assistant response
User: Second user message
Assistant: Second assistant responseAccepted labels:
User/AssistantHuman/AICustomer/Agent
WARNING
Text format may have parsing issues with multi-line messages or messages containing colons. Use structured format for production.
Status Options
| Status | Use When |
|---|---|
completed | Conversation ended naturally |
abandoned | User left without resolution |
in_progress | Conversation still ongoing |
Optional Metadata
Include additional context:
typescript
await converra.conversations.create({
agentId: 'agent_123',
content: conversationText,
status: 'completed',
llmModel: 'gpt-4o',
// Optional
companyName: 'Acme Corp', // Customer company
// Add custom fields as needed
});Best Practices
Log All Conversations
Even "bad" conversations provide valuable data:
- Abandoned sessions reveal pain points
- Errors show edge cases to handle
- Short conversations might indicate unclear system prompts
Log Promptly
Log conversations soon after they complete:
- Data is fresh and accurate
- Insights are timely
- Performance tracking is current
Use Consistent Formatting
Keep the same format across all logged conversations:
- Same labels (User/Assistant or Human/AI)
- Same line breaks
- Same structure
Handle Errors Gracefully
Don't let logging failures break your app:
typescript
try {
await converra.conversations.create({ ... });
} catch (error) {
console.error('Failed to log conversation:', error);
// Continue - logging failure shouldn't break user experience
}Viewing Logged Conversations
Dashboard
View all conversations at converra.ai/conversations
Via SDK
typescript
const { data: conversations } = await converra.conversations.list({
agentId: 'agent_123',
limit: 20
});Next Steps
- Analyzing Insights - Understand patterns
- Running Optimizations - Improve based on data
