Generation
How a message becomes an assistant reply — asynchronously by default, synchronously when you need the answer in the same request, or not at all when you only want to add context.
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.travila.ai/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": "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.travila.ai/api/v1/llm/gateway/conversation-state \
-H "X-API-Key: sk_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-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. |
Polling conversation-state as above is how you retrieve a reply today. Live token-by-token delivery over Firebase — see Real-Time Streaming — is documented ahead of availability and is not enabled yet.
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.travila.ai/api/v1/llm/gateway/send-message-sync \
-H "X-API-Key: sk_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-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.travila.ai/api/v1/llm/gateway/append-message \
-H "X-API-Key: sk_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-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.