Skip to main content

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

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

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

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