Skip to navigation

Chat sessions and widget

A chat session is a conversation between one visitor and one CHAT agent. Your page or your backend opens it, sends the visitor’s messages one at a time, and reads the agent’s reply as a stream of events. When the visitor leaves, you end the session and, if you like, rate it.

The same five calls exist twice:

From a browser or widget, with a public keyFrom your systems, with a secret key
POST /v1/ai-agents/{agent_id}/chat/initPOST /ai-agents/{agent_id}/chat/init
POST /v1/ai-agents/{agent_id}/chat/streamPOST /ai-agents/{agent_id}/chat/stream
GET /v1/ai-agents/{agent_id}/chat/{call_id}/statusGET /ai-agents/{agent_id}/chat/{call_id}/status
POST /v1/ai-agents/{agent_id}/chat/{call_id}/ratePOST /ai-agents/{agent_id}/chat/{call_id}/rate
POST /v1/ai-agents/{agent_id}/chat/{call_id}/endPOST /ai-agents/{agent_id}/chat/{call_id}/end

The public calls need the matching scope on the key (chat:init, chat:stream, chat:status, chat:rate, chat:end) and a page whose origin is in the key’s domains. The examples below use the public form; swap the prefix and the header for the other.

1

Start the session

curl -X POST https://api.hams.ai/v1/ai-agents/$AGENT_ID/chat/init \
-H "X-Public-Key: $HAMS_PUBLIC_KEY" \
-H "Content-Type: application/json" \
-d '{ "dynamic_variables": { "customer_name": "Sara", "plan": "Pro" } }'
{ "call_id": "…", "chat_history": [ { "role": "assistant", "content": "Hi Sara, how can I help with your Pro plan?" } ] }

Both fields are optional: dynamic_variables are strings the agent’s prompts can use, and ip_address lets a backend pass the visitor’s address along. Keep the call_id; every other call needs it. If the agent opens the conversation, its first message is already in chat_history. When reCAPTCHA is enabled on the key, this call also needs a g-token header.

2

Send a message and stream the reply

curl -N -X POST https://api.hams.ai/v1/ai-agents/$AGENT_ID/chat/stream \
-H "X-Public-Key: $HAMS_PUBLIC_KEY" \
-H "Content-Type: application/json" \
-d '{ "call_id": "…", "content": "Can I change my billing date?" }'

The response is application/x-ndjson: one JSON object per line, each with an event and a data object, until the reply is complete.

{"event": "message_chunk", "data": {"content": "Yes, you can"}}
{"event": "message_chunk", "data": {"content": " move it from Settings > Billing."}}
{"event": "message_complete", "data": {"content": "Yes, you can move it from Settings > Billing."}}

Render message_chunk events as they arrive and replace them with message_complete, which carries the whole message. Other events tell you what the agent is doing:

EventMeaning
thinkingThe agent is working on a reply
tool, function_call, function_resultThe agent is calling a tool and got its result
node_transitionThe agent moved to the next step of its conversation flow
handoffThe conversation was handed over to another agent
client_actionThe agent asks your page to do something, such as open a link
begin_messageThe opening message of a new turn
end_conversationThe agent decided the conversation is over
errorSomething failed; data.message says what

One message at a time: sending another stream while a reply is in progress returns 410 with STREAM_BUSY.

3

Check the session

GET …/chat/{call_id}/status returns { "status": "IN_PROGRESS" }. A session is REGISTERED after init, IN_PROGRESS once messages flow, ENDED after end or after the agent’s idle timeout, and ERROR if it failed. A page that reloads can use this to decide whether to resume the session or start a new one.

4

Rate and end

curl -X POST https://api.hams.ai/v1/ai-agents/$AGENT_ID/chat/$CALL_ID/rate \
-H "X-Public-Key: $HAMS_PUBLIC_KEY" \
-H "Content-Type: application/json" \
-d '{ "call_rating": 5, "call_rating_comment": "Solved it in one message" }'
curl -X POST https://api.hams.ai/v1/ai-agents/$AGENT_ID/chat/$CALL_ID/end \
-H "X-Public-Key: $HAMS_PUBLIC_KEY" \
-H "Content-Type: application/json" \
-d '{ "end_reason": "user_ended" }'

A session can be rated once; the body of end is optional and end_reason defaults to user_ended. Ended sessions appear in the agent’s conversation log in the dashboard.

Errors you will meet

error_codeWhen
AGENT_NOT_FOUND, AGENT_NOT_CHATNo such agent in this entity, or it is a VOICE agent
CALL_NOT_FOUND, NOT_A_CHAT_SESSIONThe call_id does not belong to this agent, or is a phone call
CHAT_ALREADY_ENDEDSending to, or ending, a session that is already ENDED
STREAM_BUSYA reply is still streaming for this session
ALREADY_RATEDThe session was rated before
CHAT_STORAGE_DISABLEDThe agent’s data_storage setting keeps no transcript, so history cannot be returned
KEY_SCOPE_NOT_ALLOWED, DOMAIN_NOT_ALLOWEDThe public key may not make this call, or the page is not in its domains

Rate limits are 20 requests per minute per public key on each of init, stream, end and rate, and 500 per minute per entity on init and stream; see API conventions.

Widget configuration

The embeddable widget reads its look and behaviour from the agent’s widget configuration, a free-form JSON document you control:

curl -X PUT https://api.hams.ai/ai-agents/$AGENT_ID/widget-config \
-H "X-Secret-Key: $HAMS_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "config": { "title": "Example Store support", "primaryColor": "#092652", "position": "bottom-right" } }'

PUT creates or replaces the configuration and needs the WRITER role; GET /ai-agents/{agent_id}/widget-config returns it with id, ai_agent_id, entity_id, created, updated and presigned_urls for any assets it references.

The widget itself fetches it from the browser with the public key:

curl https://api.hams.ai/v1/ai-agents/$AGENT_ID/widget-config \
-H "X-Public-Key: $HAMS_PUBLIC_KEY" \
-H 'If-None-Match: "…"'

The response carries an ETag and short-lived Cache-Control; send the tag back in If-None-Match and you get 304 while nothing changed. The key needs the widget-config:read scope.