Chat Conversations API Reference
REST and WebSocket endpoints for the Chat service: synchronous converse, streaming conversations, Cosmos Assistant and test-session streams, WhatsApp webhook, session events, and file uploads.
Updated 2026-11-03
Verified 2026-11-03
Chat sits between clients and the ADK agent runtime. It authenticates the caller, checks the chat.initiate permission, and relays conversations to ADK over REST or WebSocket. For architecture and configuration, see Chat Service.
Local base URL: http://localhost:8108. Base URLs, headers, and error shapes are described in API Conventions.
Authentication
Every HTTP endpoint except GET /health/ requires:
| Header | Value |
|---|---|
Authorization | Bearer YOUR_TOKEN |
X-Org-Id | YOUR_ORG_ID |
Missing headers or an invalid token return 401 with {"error": "unauthorized", "message": ...}. A caller without chat.initiate in the organization gets 403; if the authorization service is unreachable, Chat returns 503. Service tokens acting for a user send X-On-Behalf-Of-User-Id.
The WebSocket endpoint takes the token at connection time, either in the Authorization header or, for browsers, as a subprotocol: Sec-WebSocket-Protocol: cosmos.v1, cosmos-auth.YOUR_TOKEN. The organization is sent in the start message. The end-user ID is always taken from the token (or the on-behalf-of header), not from the request body.
Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /health/ | Health check, returns {"status": "ok"} |
| POST | /api/v1/converse | Send one message and wait for the agent's reply |
| WS | /api/v1/converse_stream | Streaming conversation |
| POST | /api/v1/converse/whatsapp | WhatsApp webhook |
| POST | /api/v1/trig-add-event | Inject an event into an active session |
| POST | /api/v1/trig-end-session | End a session |
| POST | /api/v1/upload-file | Upload an image or PDF for a conversation |
Converse (REST)
POST /api/v1/converse
| Field | Type | Required | Description |
|---|---|---|---|
agent_id or app_id | string | One of them | Target agent or app |
agent_version, app_version | string | No | Defaults to the version matching type |
type | string | Yes | deployed or draft |
query | string | Yes | User message |
user_id | string | Yes | End-user identifier |
session_id | string | No | Continue an existing session |
user_name, user_data | string, object | No | End-user display name and metadata |
sequenceNumber | integer | No | Message sequence number |
provider_id, model_id | string | No | Model override |
project_id, project_name | string | No | Attribute the conversation to a project |
org_id, event | string | No | Optional overrides |
{
"agent_id": "AGENT_UUID",
"type": "deployed",
"user_id": "user-123",
"query": "Hello, how can you help me?",
"project_id": "PROJECT_UUID"
}
The response is the ADK converse result, including session_id. Errors return {"status": <code>, "error": "..."}; sending neither agent_id nor app_id returns 400.
Streaming Conversations (WebSocket)
WS /api/v1/converse_stream
- Open the socket with the token (header or subprotocol).
- Send a
startmessage as the first frame. Any other first frame closes the socket with1003. - Receive streamed response frames.
- Send
queryframes for follow-up messages.
The start message decides which stream you get:
start contents | Stream |
|---|---|
| Default | Agent or app conversation relayed to ADK v1 |
"channel": "cosmos_assistant" | Cosmos Assistant, relayed to ADK v2 and mapped to the same response frames |
"test_protocol": "adk.v2.test.v1" | Agent test session with structured ADK v2 events (disabled unless CHAT_V2_TEST_RELAY_ENABLED=true) |
Agent or app stream
{
"event": "start",
"agent_id": "AGENT_UUID",
"type": "deployed",
"org_id": "YOUR_ORG_ID",
"project_id": "PROJECT_UUID",
"query": "What's the weather in London?"
}
start fields: agent_id or app_id, optional agent_version / app_version, type (default deployed), org_id (required), query, user_name, user_data, and optional project_id and project_name. A browser cannot set an X-Project-Id handshake header, so project scope travels in the body.
Server frames:
{"response": {"content": "The weather in London is", "is_final": false}}
{"response": {"content": "The weather in London is 18°C and partly cloudy.", "is_final": true, "session_id": "SESSION_ID"}}
Follow-up:
{"event": "query", "query": "What about tomorrow?"}
Cosmos Assistant stream
Send "channel": "cosmos_assistant" with org_id and query. Optional fields: images, user_data, provider_id, model_id, additional_instructions, language (default English), and project_id. To resume, send "resume_session": true with session_id. If ADK no longer has the session, the final frame carries "error_code": "SESSION_NOT_FOUND", "fatal": true; start a new session instead of retrying.
Cost notices
When a session, weekly, or monthly cost limit is near or reached, Chat sends:
{"response": {"event": "cost_notice", "content": "...", "severity": "warning", "is_final": true}}
severity is warning or error. When ADK stops a session for a cost limit it closes with 4000, and Chat passes that code through; treat it as final rather than reconnecting.
Agent test sessions
When enabled, a start frame with "test_protocol": "adk.v2.test.v1" opens a two-way ADK v2 test session. It carries org_id, agent_id or app_id, optional project_id, a non-blank query, and a test_session object (schema_version, name, tool_refs, graph_ids). Unknown fields are rejected. The client receives structured events (tool_start, tool_complete, turn_complete, error, approval_required) with tool arguments and reasoning redacted, and can send query, tool_result, approval_decision, stop, or pong. Malformed frames get {"event": "error", "code": "...", "message": "...", "fatal": false}.
Close codes
| Code | Meaning |
|---|---|
1000 | Normal close (agent finished or disconnected) |
1003 | First frame was not valid JSON or not a start message |
1008 | Test protocol rejected (unsupported version or relay disabled) |
1011 | Agent connection failed or authorization service unavailable |
4000 | Cost limit reached |
4401 | Missing or invalid token, or user identity could not be resolved |
4403 | Permission denied or missing organization |
Before closing with 4401 or 4403, Chat may send {"code": 4403, "reason": "..."}.
WhatsApp Webhook
POST /api/v1/converse/whatsapp?org_id=YOUR_ORG_ID&app_id=APP_UUID&type=deployed
Content-Type: application/x-www-form-urlencoded
Requires Authorization and X-Org-Id. org_id must match X-Org-Id, and the caller needs chat.initiate on the app. Query parameters: org_id and app_id (required), app_version, and type (default deployed).
Form fields: From and Body (required), ProfileName, MediaUrl0, MediaContentType0. Supported media types are image/png, image/jpeg, and application/pdf.
Trigger Add Event
Inject an external event (for example a payment confirmation) into an active ADK session.
POST /api/v1/trig-add-event
{
"entity_name": "app-APP_UUID",
"user_id": "user-123",
"org_id": "YOUR_ORG_ID",
"session_id": "SESSION_ID",
"content": "Payment confirmed for order #12345",
"state_delta": {"payment_status": "confirmed"}
}
entity_name is app-<id> or agent-<id>. state_delta is required (send {} for none). user_id must match the token's user unless the caller is a superuser. If the user has an open stream, Chat also pushes the agent's reply to it; the response reports this in websocket_status (sent, failed, or no_connection).
Trigger End Session
POST /api/v1/trig-end-session
{"entity_name": "agent-AGENT_UUID", "user_id": "user-123", "session_id": "SESSION_ID"}
session_id is optional but recommended; without it Chat ends the session it last cached for that user and entity. user_id must match the token's user unless the caller is a superuser. Returns {"status": "success", "message": "session ended successfully", "data": null}, or "session already ended" when ADK no longer has the session.
Upload File
POST /api/v1/upload-file
Content-Type: multipart/form-data
| Field | Required | Description |
|---|---|---|
file | Yes | image/png, image/jpeg, image/jpg, or application/pdf |
userId | Yes | User ID for attribution |
Returns {"status": "success", "message": "File uploaded successfully", "data": "<signed URL>"}. Other file types return 400.
Testing
curl -s -X POST http://localhost:8108/api/v1/converse \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Org-Id: YOUR_ORG_ID" \
-H "Content-Type: application/json" \
-d '{"agent_id": "AGENT_UUID", "type": "deployed", "user_id": "test-user", "query": "Hello"}'
# Streaming, using websocat
echo '{"event":"start","agent_id":"AGENT_UUID","type":"deployed","org_id":"YOUR_ORG_ID","query":"Hello"}' | \
websocat -H "Authorization: Bearer YOUR_TOKEN" ws://localhost:8108/api/v1/converse_stream