Conversations
This guide explains how conversations work on the Travila platform — threads, messages, generation, and configuration.
Core Concepts
Threads
A thread is a conversation container. Each thread has:
- A server-generated
threadId(UUID) — use this as theconversation_keyvalue on subsequent requests - An optional
title— human-readable label you can set at creation time - Its own message history
- Independent settings and generation config
Threads are scoped to the authenticated user. One user can have many threads.
Messages
Messages are the content units within a thread. Each message has a role and content:
| Role | Description |
|---|---|
ROLE_USER | Messages from the end user |
ROLE_ASSISTANT | AI-generated responses |
ROLE_SYSTEM | System instructions (injected by the platform or via settings) |
ROLE_TOOL | Results from MCP tool calls |
Conversation State
Every thread maintains state that includes:
- Message history (
messageHistory) — the full ordered list of messages - Generation config — model, temperature, max tokens, etc.
- Settings — system prompt, context management, interrupt policy
- Run status —
activeRunId(therunIdof the in-progress run, or empty when idle) - Pending tool approvals — tools waiting for user approval before execution
Conversation Lifecycle
1. Create a Thread
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/create-thread \
-H "X-API-Key: sk_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-H "Content-Type: application/json" \
-d '{
"title": "Support Chat"
}'
The response returns { "thread": { "threadId": "<uuid>", ... } }. Use threadId as the conversation_key on all subsequent requests on this thread. X-On-Behalf-Of is required when calling with a backend secret key.
2. Send a Message
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/send-message \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "support-chat-001",
"user_message": {
"role": "ROLE_USER",
"content": [
{ "type": "CONTENT_PART_TYPE_TEXT", "content": "How do I reset my password?" }
]
}
}'
When you send a message:
- The message is appended to the thread's history.
- A generation run starts asynchronously; the HTTP response returns with a
runId. - The LLM processes the conversation history and generates a response.
- If the model calls tools, tool execution happens automatically (or awaits approval).
- The assistant response is appended to
messageHistory, annotated withgeneratedBy: <runId>. - Internal platform services also receive the response as token chunks on an internal Kafka topic — see Internal Event Stream.
If a prior run was still in progress and the thread's interrupt_policy is INTERRUPT (cancel ongoing), send-message also returns "interruptedPriorRun": true alongside the new runId.
To retrieve the completed assistant reply from an external client, poll conversation-state — see Async Generation.
3. Retrieve State
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/conversation-state \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "support-chat-001"
}'
Returns messageHistory, activeRunId, usage, model, settings, generation config, and context-management settings. When a run is in progress, activeRunId holds the active run's ID; once settled, activeRunId is empty (or absent from the response) and the assistant message appears in messageHistory. See Async Generation for the full polling recipe.
4. List Threads
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/list-threads \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{}'
Returns all threads for the authenticated user.
Async Generation
send-message returns immediately with a runId. Generation runs asynchronously in the background. To retrieve the completed assistant message from an external client, poll conversation-state until the run settles.
Polling recipe
- Call
send-messageand capturerunIdfrom the response. - Poll
conversation-stateevery 2 seconds, up to 60 seconds total. - Stop when
activeRunIdis empty (or absent from the response). (Fallback signal: an assistant message inmessageHistorywheregeneratedByequals therunIdyou captured.) - Read the assistant reply from
messageHistory.
Worked example
Step 1 — send the message:
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/send-message \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "b81d5345-c1f9-4fb9-b558-a6327c75b842",
"user_message": {
"role": "ROLE_USER",
"content": [
{ "type": "CONTENT_PART_TYPE_TEXT", "content": "Hello, what can you help me with?" }
]
}
}'
Response:
{
"runId": "64403669-5989-4ec3-ad9c-d84223f9679f"
}
Step 2 — poll conversation-state:
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/conversation-state \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "b81d5345-c1f9-4fb9-b558-a6327c75b842"
}'
Mid-generation response (while generation is in progress):
{
"messageHistory": [
{
"role": "ROLE_USER",
"content": [{ "type": "CONTENT_PART_TYPE_TEXT", "content": "Hello, what can you help me with?" }],
"timestamp": "2026-04-23T22:43:44.123Z",
"messageId": "2a1f33ce-1abc-4a5d-9e22-1c0d1a2b3c4d",
"sequence": "1"
}
],
"activeRunId": "64403669-5989-4ec3-ad9c-d84223f9679f"
}
Settled response (typically after 7–11 seconds):
{
"messageHistory": [
{
"role": "ROLE_USER",
"content": [{ "type": "CONTENT_PART_TYPE_TEXT", "content": "Hello, what can you help me with?" }],
"timestamp": "2026-04-23T22:43:44.123Z",
"messageId": "2a1f33ce-1abc-4a5d-9e22-1c0d1a2b3c4d",
"sequence": "1"
},
{
"role": "ROLE_ASSISTANT",
"content": [{ "type": "CONTENT_PART_TYPE_TEXT", "content": "I can assist you with..." }],
"timestamp": "2026-04-23T22:43:51.653Z",
"messageId": "3f2d44de-8db6-4f67-8e51-5c600902491b",
"sequence": "2",
"generatedBy": "64403669-5989-4ec3-ad9c-d84223f9679f",
"usage": {
"promptTokens": 359,
"completionTokens": 65,
"totalTokens": 424
},
"model": "google/gemini-3.1-flash-lite"
}
],
"activeRunId": ""
}
Field reference
| Field | Returned by | Meaning |
|---|---|---|
runId | send-message | Client's handle to this generation run. |
activeRunId | conversation-state | The run currently executing; empty string (or absent) when idle. |
generatedBy | each assistant message in messageHistory | The runId that produced that message — use it to correlate a specific request with its reply. |
For live token-by-token rendering, subscribe to the real-time stream over Firebase — see Real-Time Streaming. Polling conversation-state (above) is the simpler alternative when you don't need live tokens or don't use Firebase.
Synchronous Generation
When you need the assistant reply in the HTTP response itself — typically for structured output (a JSON object, a classification) rather than an interactive chat bubble — use send-message-sync. It blocks until generation finishes and returns the messages inline, so there's nothing to poll or stream.
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/send-message-sync \
-H "X-API-Key: sk_your_key_here" \
-H "Idempotency-Key: 6b1e7c2a-9f3d-4a11-8c5e-2d7a1b4f8e90" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "b81d5345-c1f9-4fb9-b558-a6327c75b842",
"user_message": {
"role": "ROLE_USER",
"content": [
{ "type": "CONTENT_PART_TYPE_TEXT", "content": "Summarize this thread as JSON." }
]
}
}'
Response — the generated messages, with token usage, returned directly:
{
"runId": "9d4c...",
"status": "AGENT_STATUS_COMPLETED",
"messages": [
{ "role": "ROLE_ASSISTANT", "content": [{ "type": "CONTENT_PART_TYPE_TEXT", "content": "…" }], "generatedBy": "9d4c..." }
],
"aggregateUsage": { "promptTokens": 412, "completionTokens": 88, "totalTokens": 500 }
}
status is one of AGENT_STATUS_COMPLETED, AGENT_STATUS_FAILED, or AGENT_STATUS_QUEUED. For JSON/structured replies, set response_format in the generation config.
Idempotency-KeyBecause this call can run for several seconds, send an Idempotency-Key header (a UUID). If the request is retried with the same key within 24 hours, the platform returns the original result instead of generating again — avoiding duplicate, billed generations on a network retry.
Append a Message
append-message adds a message to a thread's history without running the model. Use it to inject context — a system note, an event from another source, or a record of something that happened outside the chat — that later generations should see, but which should not itself trigger a reply.
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/append-message \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "b81d5345-c1f9-4fb9-b558-a6327c75b842",
"message": {
"role": "ROLE_USER",
"content": [
{ "type": "CONTENT_PART_TYPE_TEXT", "content": "CONTEXT UPDATE: user completed onboarding." }
]
}
}'
The field is message, not user_message (mixing the two returns 400). The response is an empty object {} — no generation runs.
Generation Config
Control how the AI generates responses by updating the default generation config.
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/update-default-generation-config \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "support-chat-001",
"generation_config": {
"model": "anthropic/claude-sonnet-5",
"temperature": 0.7,
"max_tokens": 2048,
"top_p": 0.9
}
}'
Key generation config fields:
| Field | Description | Default |
|---|---|---|
model | LLM model to use (OpenRouter model ID) | Platform default |
temperature | Randomness (0.0 = deterministic, 2.0 = creative) | 1.0 |
max_tokens | Maximum tokens in the response | Model default |
top_p | Nucleus sampling threshold | 1.0 |
Available Models
Set model (or the models fallback list) to any of the following OpenRouter model IDs:
| Model ID | Provider | Notes |
|---|---|---|
google/gemini-2.5-flash | Platform default | |
google/gemini-3.5-flash | ||
google/gemini-3.1-flash-lite | ||
google/gemini-3.1-pro-preview | ||
google/gemini-3-flash-preview | ||
anthropic/claude-sonnet-5 | Anthropic | |
anthropic/claude-sonnet-4.6 | Anthropic |
Any model ID not in the OpenRouter catalog is rejected — see Model Routing & Pre-Filter for validation and capability-based filtering.
Reasoning
Models that support chain-of-thought reasoning (e.g., google/gemini-3.1-pro-preview, anthropic/claude-sonnet-5) can expose their internal thinking process. Configure reasoning via the reasoning_options field on the generation config:
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/update-default-generation-config \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "support-chat-001",
"generation_config": {
"model": "google/gemini-3.1-pro-preview",
"reasoning_options": {
"effort": "EFFORT_MEDIUM",
"include_reasoning_history": true
}
}
}'
| Field | Description | Default |
|---|---|---|
effort | How much reasoning the model should perform. Values: EFFORT_NONE, EFFORT_MINIMAL, EFFORT_LOW, EFFORT_MEDIUM, EFFORT_HIGH, EFFORT_XHIGH | EFFORT_UNSPECIFIED (provider default) |
max_tokens | Maximum tokens the model may use for reasoning | Model default |
exclude | If true, reasoning content is not included in the response | false |
include_reasoning_history | Include reasoning from previous turns in multi-turn requests for provider continuity | true |
When reasoning is enabled, assistant messages may contain content parts with type: CONTENT_PART_TYPE_REASONING alongside the normal CONTENT_PART_TYPE_TEXT parts. The reasoning parts contain the model's internal thinking process.
Conversation Settings
Update the conversation's system prompt, interrupt policy, and other settings.
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/update-settings \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "support-chat-001",
"settings": {
"system_prompt": "You are a helpful customer support agent for Acme Corp.",
"interrupt_policy": "QUEUE"
}
}'
System Prompt
The system_prompt is prepended to every LLM request for this conversation. Use it to set the AI's persona, rules, and context.
Interrupt Policy
Controls what happens when a user sends a new message while a generation run is already in progress:
| Policy | Behavior |
|---|---|
QUEUE | Queue the new message and process it after the current run completes |
INTERRUPT | Cancel the current run and start a new one with the latest message |
Context Management
For long conversations, the message history can exceed the model's context window. Context management settings control how this is handled.
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/update-context-management-settings \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "support-chat-001",
"context_management_settings": {
"strategy": "SLIDING_WINDOW",
"max_history_messages": 50
}
}'
| Strategy | Description |
|---|---|
SLIDING_WINDOW | Keep the most recent N messages |
SUMMARIZE | Summarize older messages to preserve context while reducing token count |
Internal Event Stream (Kafka)
During generation, the platform publishes token chunks to an internal Kafka topic. This transport is consumed by internal platform services — it is not directly accessible to external API consumers on api.yocaso.dev.
External clients don't consume Kafka directly. For real-time assistant tokens, subscribe to the Firebase real-time stream — which the platform mirrors from this same Kafka transport — or poll conversation-state (see Async Generation).
For reference, each internal Kafka chunk carries a type:
| Stream Chunk Type | Description |
|---|---|
STREAM_CHUNK_TYPE_TEXT | Regular text content token |
STREAM_CHUNK_TYPE_TOOL_CALL | Incremental tool call data |
STREAM_CHUNK_TYPE_REASONING | Reasoning/thinking token (when reasoning is enabled) |
When reasoning is enabled, STREAM_CHUNK_TYPE_REASONING chunks are emitted first (the model's thinking process), followed by STREAM_CHUNK_TYPE_TEXT chunks (the visible response). The final chunk includes is_final: true, finish_reason, and token usage with completion_tokens_details breaking down reasoning vs text tokens.
Tool Calling
When the LLM decides to use a tool (via MCP), the platform can either:
- Auto-execute the tool and feed results back to the LLM
- Request approval from the user before execution
Checking Pending Approvals
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/list-pending-approvals \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "support-chat-001"
}'
Submitting Approvals
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/submit-tool-approvals \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "support-chat-001",
"approvals": [
{
"tool_call_id": "call_abc123",
"approved": true
}
]
}'
Client-Side Tools
For tools that should execute on the client (e.g., UI actions), submit results back to the conversation:
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/submit-client-tool-results \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "support-chat-001",
"tool_results": [
{
"tool_call_id": "call_xyz789",
"result": "{\"status\": \"confirmed\"}"
}
]
}'
See the MCP Tools Guide for more on tool calling.
Voice Sessions
Voice sessions are under active development and are not enabled on production accounts yet. The API below is documented for preview and is subject to change. Reach out to your account team if you'd like early access.
Create a real-time voice session for a conversation using Daily and Pipecat:
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/create-daily-session \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "support-chat-001"
}'
The response includes a Daily room URL and token for the client to join the voice session.
User Impersonation
Backend services with the users:impersonate scope can operate on behalf of specific users:
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/send-message \
-H "X-API-Key: sk_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "support-chat-001",
"user_message": {
"role": "ROLE_USER",
"content": [
{ "type": "CONTENT_PART_TYPE_TEXT", "content": "Hello" }
]
}
}'
See the API Key Integration Guide for details on scopes.
Related
- Quickstart — Make your first API call
- Real-Time Streaming — Live token delivery over Firebase
- Building a Mobile App — End-to-end client integration
- MCP Tools Guide — Tool calling in conversations
- Memory Guide — Semantic memory for conversations
- LLM API Reference — Full endpoint reference