conversation_id and the agent carries on with everything said and done so far:
Sending a message
POST /v1/agents/{agent_id}/responses takes:
Messages while it works
You can send the next message while a reply is still going, as people do in the console and the website widget. It’s queued for that reply rather than refused:- The call answers
202at once, streamed or not: the reply as it is now, andqueued_message, your message as an item withqueued: true. - The reply reads it at its next step: once the tool or task it’s running returns, or, if it was writing its answer, it carries on and answers your message too before it ends. It’s one reply, so follow the answer on that reply’s stream or with
GET /v1/responses/{response_id}. - A task the reply is running reads it at its next step too, so “use the blue one” changes what the task does now. Anything the task isn’t for is left to the agent, which reads the message once the task returns.
- Once the agent (or its task) has read it, the item loses
queued, and a stream hasinput.delivered. A reply that’s cancelled or fails before it reads one keeps it in the conversation: the next turn reads it. - Answering a form while a reply is still going is a
409: wait for the reply, or cancel it.
Responses
The agent’s answer is the last
assistant_message in output.
Long turns
A turn that runs a browser task can take a minute or more. Withoutstream, the call waits up to 55 seconds; if the turn is still going then, it answers with status: "in_progress" (and an empty output). Follow it with GET /v1/responses/{response_id}?wait_ms=55000, which answers as soon as it ends, or after wait_ms with what it has written so far:
completed with the agent saying it stopped there. Send another message to carry on.
Cancelling
POST /v1/responses/{response_id}/cancel stops a turn. Items it already wrote stay, tasks it started are stopped, and the tokens it used so far are charged. The conversation goes on from there: the agent’s next turn still reads the stopped message and its attachments, what it had done, and that it was stopped. It answers at once, and the response may say in_progress for a moment until the turn has stopped. Cancelling a finished response does nothing.
Streaming
With"stream": true the answer is a stream of server-sent events: each is event: <type> and data: <JSON> (the JSON has type too). A : ping comment comes every 15 seconds.
stream.
Items
GET /v1/conversations/{conversation_id}/items lists a conversation’s items, oldest first, a page at a time (limit up to 500, cursor). Each has object: "conversation_item", its seq (1, 2, 3… in the conversation), the response_id of its turn, type and at:
GET /v1/conversations lists the org’s conversations, most recent first, a page at a time; q searches their titles, agent_id keeps one agent’s, and source=phone keeps the phone calls (metadata.source is phone, with caller and number) and source=chat the rest. A task you start has a conversation of its own (metadata.task_id, titled “Task: ” and the start of its goal): it’s listed only while something in it waits for someone, and waiting=approval always includes it. GET /v1/conversations/{conversation_id} is one: its agent_id, the agent_version it started on (null for the draft), title (the start of the first message), metadata, session_id (its cloud browser, once it has one), created_at and last_item_at.
Conversations, their items and their uploads are kept for the org’s history retention (90 days after the last message, by default; admins change it in Settings → General).
What the agent can do in a turn
The agent answers from its instructions and uses its tools as it needs them:
A change to the agent (a new version, or the draft you’re chatting with) applies from the conversation’s next message. The procedures it was told about stay as they were when it started: one added while it’s going is listed from the agent’s next conversation.
Ending a conversation
When the person has what they came for and says goodbye, the agent ends the conversation with itsend_conversation tool (and a reason, for your team). The conversation’s status becomes ended, with ended_at and end_reason; the stream sends conversation.ended (with reason); and the conversation.ended webhook is sent. From then on it takes no more messages: a new one gets 409 with code: "conversation_ended", so start a new conversation instead. A phone call hangs up once the goodbye is said, and the website widget starts a new chat with the person’s next message. In the console it shows as a line across the conversation, with the reason.
Forms
When the agent needs details from the person (a serial number, a country, which plan), it shows a form rather than asking in text: aui item and event with a title and up to 8 fields. Each field has an id, a label and a kind: choice, multi_choice, yes_no, text, number, slider or date (with options, min, max, step, unit, default and allow_other where they apply). a2ui is the same form as A2UI v0.9 messages, for clients that render A2UI. The turn ends after the form, waiting for the person.
Answer it with the next message, sending action with the form’s surface_id and the values by field id:
choiceandyes_notake a string (yes_no:"yes"or"no"); withallow_other, send the typed answer as"<id>_other".multi_choicetakes a list.numberandslidertake a number within their range.datetakesYYYY-MM-DD.texttakes a string.- A field left out is recorded as not answered; a value that doesn’t fit the field is a
400. A form can be answered once (409after that). - The answers reach the agent as the person’s message, and
input, if you send it too, is added after them. - A voice agent can just pass the caller’s words as
inputinstead: the agent reads them like any message, and the form stays unanswered.
Visual answers
A long answer that reads better as UI (a status, options to compare, a table, steps) is also laid out as UI: itsassistant_message item has a view, and a message.view event comes just before the turn ends. The console’s chat and the website widget show the view; the text stays the answer, and it’s what the agent, evals and your own code read. It works for every agent, procedure and model: once the agent has answered, a small model reads the answer and lays it out from a fixed set of blocks, or leaves it as text. It only rearranges the answer: a view with a number, date, price or code the text doesn’t have is dropped.
title is a card. Blocks: text, facts (label and value pairs), table (columns, rows), options (side by side; pick is what the person says by choosing one), list (style: bullet, check or number), callout (tone, text), badges, bars (numbers to compare) and buttons (message is what the person says by pressing one). Tones are neutral, good, warning, bad and info. A choice or a button just sends its words as the person’s next message. Phone calls, eval runs and Coach aren’t laid out. Set it per agent with tools.visual: turn it off, choose which cards it may use, leave out picks and buttons, or tell it how to lay answers out in your own words.
Tasks from a conversation
When the answer lives in a website or a desktop app, the agent runs a task: it gives a worker a goal (“Find the warranty end date for serial PF2ABCDE on support.example.com”), the worker does it on the conversation’s browser or one of the agent’s computers, and reports back what it found. The worker has the agent’s tools, guardrails and the procedure for the job, but nobody at its screen, so it never asks questions: it says in its result what it needs, and the agent asks the person.- Each task is a
taskitem in the conversation (andtask.*events in a stream), and an ordinary task:GET /v1/tasks/{task_id}has its events, result, recording and replay, and itsmetadatahas theconversation_idandresponse_id. - Watch it live: the console shows it playing inside the conversation, and
live_urlopens it. - The person can still write while it runs: the task reads their message at its next step.
- It’s billed for its tokens like any task, and its cost shows in the response’s
usage.tasks. - When the org is at its limit of tasks or browsers at once, the agent waits up to a minute for room, then tells the person it couldn’t run it.
Progress
A task can take a minute or more, so the agent gives each one a plan: two to five short steps in the person’s words (“Looking up your device”, “Checking your warranty”), its procedure’s main steps when it follows one. The worker says which step it’s on as it goes. The task item hasplan and plan_step (the step it’s on, which only moves forward), and a stream’s task.step events carry plan_step. The website widget shows the plan to visitors as a progress bar with the steps ticked off, and the console’s chat shows it on the task. Show it in your own app the same way: it’s written for the person, unlike goal and each step’s text, which are for your team.
Approvals
Some things need a yes before they happen: a task that follows a procedure whose Approval is on (also when the agent read it and starts the task without naming it), an HTTP tool withapproval: "always", and, with the agent’s guardrails in supervised mode (the default), a task step that needs approval, such as submitting a form or placing an order. The request is asked in the conversation, and the reply pauses:
- It ends with
status: "requires_action"(streamed:response.requires_action), with anapprovalitem inoutput: what will happen, in Eager’s words, never the model’s. A task waiting on it ispaused. - Your webhook gets
conversation.approval_required. - Nothing runs until it’s decided. The person can decide it on the website widget, unless the request is your team’s (
by: "team"); your team decides in the console (the agent’s chat, or Conversations) or with the API.
conversation_id and approval, and no input.
id, streamed with "stream": true). Approved, the step runs once; denied, it doesn’t, and the agent hears so, with your note if you sent one (up to 500 characters). A client that draws A2UI can send the request’s own Approve or Deny instead: an action on its surface_id, named approve or deny. A question a task asks is a question item, answered the same way with answer: { question_id, answer }.
Or decide with POST /v1/conversations/{conversation_id}/decisions. It takes the same approval, answer or action, names no agent, and answers at once with the decision ({ "object": "decision", "approval": … }), while the paused reply carries on by itself (follow it with GET /v1/responses/{response_id}). It’s the way to decide for a task you started without an agent, whose conversation has none.
- Who decided is the decision’s
by:team(an API key’skey_id, or a member’semail),person(the person in the chat: a visitor on the website widget, or the customer at the computer, in the desktop app),auto(guardrails inautomode),refused(unattendedmode),timeout(nobody decided within the agent’sapproval_timeout_minutes) ormoved_on. Each decision goes to theconversation.approval_decidedwebhook and the audit log, except a request the guardrails decide as it’s asked (auto,refused): that asked nobody, so it’s only in the conversation, and the website widget doesn’t show it. - Whose it is:
by: "team"is your team’s to decide. A run only your team may approve keeps its task your team’s: every step that task hands back isby: "team"too. - The first decision is used. A later one gets
409withcode: "already_decided"anddecided_by. - A new message instead declines what’s open (
moved_on), ends the paused replycancelled, stops the tasks waiting on it, and the agent answers the new message. Cancelling the paused response does the same. - Waiting: a request waits until someone decides it, or until the agent’s approval timeout, if it has one. A task paused on one holds its browser or computer for up to 30 minutes, then lets go; approved after that, a new task starts from the same goal and the reply follows it (it may do that one step, once, and asks again for anything else); denied, the agent hears it was denied.
- In
automode requests are approved without asking (and recorded); inunattendedmode they’re refused. See Guardrails.
Attachments
People can send files with a message. Upload each one first, then send its id inattachments:
- A file can be up to 10 MB (an image 5 MB).
kindisimage(PNG, JPEG, GIF, WebP),pdf,text(CSV, JSON, Markdown and other text) orother. - Every model sees images and PDFs and reads text files. Other files, such as spreadsheets, need Run code on a Claude or GPT model; sending one to an agent without it is a
400withcode: "attachment_not_supported". - An upload nobody sends expires after a day. Once sent, it belongs to that conversation (it can’t be sent in another one) and is kept and deleted with it.
GET /v1/uploads/{upload_id}is the upload,GET /v1/uploads/{upload_id}/contentthe file, andDELETE /v1/uploads/{upload_id}deletes one that hasn’t been sent.- Files the agent’s code makes for the person are uploads too (
source: "agent"), listed in the code run’sfiles: download them with/content.
Errors
See Errors for the error shape.