Skip to main content

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 the conversation_key value 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:

RoleDescription
ROLE_USERMessages from the end user
ROLE_ASSISTANTAI-generated responses
ROLE_SYSTEMSystem instructions (injected by the platform or via settings)
ROLE_TOOLResults 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 statusactiveRunId (the runId of 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:

  1. The message is appended to the thread's history.
  2. A generation run starts asynchronously; the HTTP response returns with a runId.
  3. The LLM processes the conversation history and generates a response.
  4. If the model calls tools, tool execution happens automatically (or awaits approval).
  5. The assistant response is appended to messageHistory, annotated with generatedBy: <runId>.
  6. 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

  1. Call send-message and capture runId from the response.
  2. Poll conversation-state every 2 seconds, up to 60 seconds total.
  3. Stop when activeRunId is empty (or absent from the response). (Fallback signal: an assistant message in messageHistory where generatedBy equals the runId you captured.)
  4. 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

FieldReturned byMeaning
runIdsend-messageClient's handle to this generation run.
activeRunIdconversation-stateThe run currently executing; empty string (or absent) when idle.
generatedByeach assistant message in messageHistoryThe runId that produced that message — use it to correlate a specific request with its reply.
Real-time streaming

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-Key

Because 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:

FieldDescriptionDefault
modelLLM model to use (OpenRouter model ID)Platform default
temperatureRandomness (0.0 = deterministic, 2.0 = creative)1.0
max_tokensMaximum tokens in the responseModel default
top_pNucleus sampling threshold1.0

Available Models

Set model (or the models fallback list) to any of the following OpenRouter model IDs:

Model IDProviderNotes
google/gemini-2.5-flashGooglePlatform default
google/gemini-3.5-flashGoogle
google/gemini-3.1-flash-liteGoogle
google/gemini-3.1-pro-previewGoogle
google/gemini-3-flash-previewGoogle
anthropic/claude-sonnet-5Anthropic
anthropic/claude-sonnet-4.6Anthropic

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
}
}
}'
FieldDescriptionDefault
effortHow much reasoning the model should perform. Values: EFFORT_NONE, EFFORT_MINIMAL, EFFORT_LOW, EFFORT_MEDIUM, EFFORT_HIGH, EFFORT_XHIGHEFFORT_UNSPECIFIED (provider default)
max_tokensMaximum tokens the model may use for reasoningModel default
excludeIf true, reasoning content is not included in the responsefalse
include_reasoning_historyInclude reasoning from previous turns in multi-turn requests for provider continuitytrue

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:

PolicyBehavior
QUEUEQueue the new message and process it after the current run completes
INTERRUPTCancel 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
}
}'
StrategyDescription
SLIDING_WINDOWKeep the most recent N messages
SUMMARIZESummarize 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 TypeDescription
STREAM_CHUNK_TYPE_TEXTRegular text content token
STREAM_CHUNK_TYPE_TOOL_CALLIncremental tool call data
STREAM_CHUNK_TYPE_REASONINGReasoning/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

In progress — not yet generally available

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.