A chat is one text session with a text agent, the text counterpart of a call. Start it with POST /v1/chats, send messages with POST /v1/chats/{chat_id}/messages (the reply comes back on that request, as JSON or a stream), and end it with POST /v1/chats/{chat_id}/end. Prompt, tools and handoffs behave as they do on a call. A chat stays open until the agent’s end_call tool or POST /v1/chats/{chat_id}/end ends it. One the person stops writing in ends 24 hours after their last message (close_reason: "inactivity_timeout"), and only then gets its analysis and session.completed webhook. If you know your user has left, end the chat yourself. Chats also start when a message arrives on Telegram or WhatsApp. Those chats are driven by the channel: sending a message to one over the API, or minting a token for it, is refused with 409.

Start a chat

uuid
The text agent that answers (published version unless agent_version says otherwise). agent, agent_version, agent_override and agent_team work as on a call; see per-call configuration. A non-text agent is refused.
string
Your stable id for the person, 1–256 characters. Must not start with a reserved prefix. It is what links a person’s chats for memory and history. Omit it for a single-use key with no past.
object
What the agent knows about the person, read as {{userdata.field}}. Merged onto the contact’s record, so it carries into later chats. See userdata.
object
String values for the agents’ declared {{vars.*}}, fixed for this chat. See variables.
Response 201:
started_at stays null until the first message; nothing runs or is charged before then.
  • Starting a chat ends the contact’s open chat with the same agent (close_reason: "replaced"). A contact can hold open chats with different agents.
  • A workspace with no credit is refused with 402.

Send a message

Response 200, the whole turn:
Items are trimmed here; each is a full item. The request waits up to 120 seconds. A running turn finishes in the background; read the reply later from GET /v1/chats/{chat_id}/items. A message to an ended chat is refused with 409. The latest message wins. If message B arrives while A is still being answered, A’s request returns status: "canceled" with superseded_by_item_id set to B, and B’s request returns the reply. A superseded message is not charged.

Stream the reply

With "stream": true the same request answers as server-sent events: Each frame carries its name in an event field. The stream stays open until the turn ends, with a keep-alive comment every 15 seconds. turn.result is read from the database, so it is authoritative even if a delta was dropped. A refused request (ended chat, a file over 20 MB) is a normal HTTP error, not a stream. Frame shapes: streaming.

End a chat

Returns 202 with the chat, marked ended at once (the next message is a 409). Ending an ended chat is a no-op. Then, in the background:
  1. The agent’s on_exit hook runs; anything it says is saved to the chat’s items.
  2. Analysis runs (on by default for text agents) and fills analysis.
  3. The chat is priced and session.completed is sent. See webhook events.

From a browser

Never put a personal access token in browser code. Your server starts the chat, mints a chat token for it, and passes the response to the page:
(SDKs: talqing.chats.tokens.create(...).) The token lasts one hour, is pinned to one chat, and is refused with 409 for an ended chat. As a Bearer token, from any origin, it opens exactly four routes for that chat: GET /v1/chats/{chat_id}, GET /v1/chats/{chat_id}/items, POST /v1/chats/{chat_id}/messages and POST /v1/chats/{chat_id}/end. A token holder is treated as the customer: items are only the customer_visible ones (no tool calls or handoffs), and the chat reads back with cost: null, analysis: null and userdata: {}. TalqingChat from @talqing/client wraps those routes. It streams every send and calls fetchToken again shortly before expiry, so have your endpoint mint a token for the same chat:
chat.items() reloads the history after a page refresh; chat.end() ends the chat. A browser cannot start a chat or choose a contact_key. Full client reference: React SDK.

Files

Attach up to four files per message, of any type, as base64 data: URLs of up to 20 MB each:
The type is read from the bytes, never from the data: mime. What the agent does with each file: Every file is stored either way: text and files arrive as one item and one turn, and come back on the item’s attachments, each with a signed URL and url_expires_at. A file is refused with 400 only when it cannot be stored: empty, over 20 MB, or not a valid data URL. Limits: documents. In a browser, talqingFileDataUrl(file) from @talqing/client checks a File, downscales a photo and encodes it into the data_url.

Voice notes

A text agent hears voice notes only when you give it a speech-to-text model: config.stt, off (null) by default. Each note is transcribed in one request, on your own provider key, before the agent replies, so only models with file_transcription set in GET /v1/catalog qualify: today every xAI, Sarvam, Raya, OpenAI gpt-transcribe, ElevenLabs scribe_v2, Deepgram Nova-3 and OpenRouter model. config.language applies to it as on a call; Auto detects.
The transcript is stored on the attachment (status: "transcribed", transcript, duration_seconds), so every later agent in the conversation reads it too. A note that arrived while the agent had no stt stays pending and is transcribed on the next turn of an agent that has one. A file that cannot be decoded, or that the provider refuses, is failed with a failed_reason. Transcription is billed per second of audio at the model’s rate, as a line on the chat’s cost.

Read chats back

Re-running or previewing analysis on an ended chat: call analysis. A chat is a session of its conversation, so it also appears under GET /v1/conversations/{conversation_id}/sessions beside calls. See conversations.

Items

Items also carry session_id, agent_id, agent_version, attachments and per-turn metrics.

Hooks, handoffs and memory

  • on_enter runs once, on the first message; on_exit runs once, when the chat ends.
  • A handoff lasts for the rest of the chat: the new agent answers every later message. Within a chat the agent sees the whole chat so far, or what the handoff’s context passed it plus everything since. See handoffs.
  • What a new chat knows of earlier ones is the agent’s conversation.context (none, summary, transcript), as on calls. See conversation memory.

Cost and retention

  • $0.0001 per answered message (turn finished done) is the platform fee on text, drawn from credits. canceled and error turns are free. Model spend goes to your provider key. See pricing and credits.
  • cost fills in while the chat is open and is final once it ends.
  • Credit is checked at start (402) and again when a chat resumes after sitting idle. Out of credit then, the turn returns status: "error" and the agent does not run.
  • Under a retention policy, content is deleted retention_days after the chat ends. Open chats are never purged.

Next

Website chat guide

A text agent in your own React app, end to end.

Conversations

Sessions, items and a person’s whole history.