> ## Documentation Index
> Fetch the complete documentation index at: https://docs.guidinghand.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversations

> Talk to an agent one message at a time. Each call to /responses is one turn: the agent answers with its files, procedures and tools, and runs browser and computer tasks when the answer needs them.

A **conversation** is the messages between a person (or your system) and one agent, with everything the agent did in between: the tools it called, the forms it showed, the browser and computer tasks it ran. Each message you send is one **response**: one turn, from your message to the agent's reply.

```bash theme={null}
curl https://guidinghand.ai/v1/agents/support/responses \
  -H "Authorization: Bearer $EAGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "input": "Is order 4417 out for delivery yet?" }'
```

```json theme={null}
{
  "object": "response",
  "id": "resp_k3Qm8ZpL2vXa",
  "conversation_id": "conv_9qR2mW7cLp0e",
  "status": "completed",
  "error": null,
  "output": [
    { "object": "conversation_item", "seq": 1, "response_id": "resp_k3Qm8ZpL2vXa", "type": "user_message", "text": "Is order 4417 out for delivery yet?", "at": "2026-10-18T10:00:00.120Z" },
    { "object": "conversation_item", "seq": 2, "response_id": "resp_k3Qm8ZpL2vXa", "type": "tool_call", "call_id": "toolu_01A", "name": "lookup_order", "arguments": { "order": "4417" }, "label": "Called lookup_order", "at": "2026-10-18T10:00:01.904Z" },
    { "object": "conversation_item", "seq": 3, "response_id": "resp_k3Qm8ZpL2vXa", "type": "tool_result", "call_id": "toolu_01A", "name": "lookup_order", "output": "{\"status\":\"out_for_delivery\",\"eta\":\"today by 6 pm\"}", "at": "2026-10-18T10:00:02.311Z" },
    { "object": "conversation_item", "seq": 4, "response_id": "resp_k3Qm8ZpL2vXa", "type": "assistant_message", "text": "Yes: order 4417 is out for delivery and should arrive today by 6 pm.", "at": "2026-10-18T10:00:03.552Z" }
  ],
  "usage": { "input_tokens": 6120, "cached_tokens": 0, "cache_write_tokens": 5800, "output_tokens": 94, "reasoning_tokens": 0, "tasks": [] },
  "created_at": "2026-10-18T10:00:00.118Z",
  "ended_at": "2026-10-18T10:00:03.560Z"
}
```

Send the next message with the same `conversation_id` and the agent carries on with everything said and done so far:

```bash theme={null}
curl https://guidinghand.ai/v1/agents/support/responses \
  -H "Authorization: Bearer $EAGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "conversation_id": "conv_9qR2mW7cLp0e", "input": "Can you change the delivery address to 12 Oak Street?" }'
```

Anyone in the org can talk to its agents, in the console (the agent's test chat, and **Conversations**) or with an API key.

## Sending a message

`POST /v1/agents/{agent_id}/responses` takes:

| Field | What it does |
| - | - |
| `input` | The message, as text, up to 8,000 characters. Required, unless the message carries `attachments` or answers a form (`action`). |
| `conversation_id` | The conversation to continue. Leave it out to start a new one. It must be a conversation with this agent. |
| `stream` | `true` to get the turn as it happens, as [server-sent events](#streaming). Otherwise the call answers with the finished response, or after 55 seconds with one that's still going. |
| `attachments` | Up to 10 [uploads](#attachments) (`upl_…` ids) sent with the message. |
| `version` | `"draft"` or a version number, to run this turn on the agent's draft or an earlier version. Leave it out for the published version. It applies to this turn only: a later turn without it runs the published version. |
| `metadata` | Your own values for a new conversation, such as your call or ticket id (a JSON object, up to 4,000 characters). Only used when the conversation is created. |
| `opening` | The agent's first message for a new conversation, up to 500 characters, when the agent has no [`greeting`](https://docs.guidinghand.ai/concepts/agents#greeting) of its own. Use it when your voice agent already greeted the caller, so the history matches what they heard. |
| `action` | Answers a [form](#forms) the agent showed, or decides a [request](#approvals) on its surface. |
| `approval` | Decides an [approval](#approvals) in the conversation: `{ approval_id, decision, note? }`, with `conversation_id` and no `input`. |
| `answer` | Answers a [question](#approvals) in the conversation: `{ question_id, answer }`, the same way. |

### 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 `202` at once, streamed or not: the reply as it is now, and `queued_message`, your message as an [item](#items) with `queued: 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](#tasks-from-a-conversation) 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 has `input.delivered`. A reply that's [cancelled](#cancelling) or fails before it reads one keeps it in the conversation: the next turn reads it.
* Answering a [form](#forms) while a reply is still going is a `409`: wait for the reply, or cancel it.

## Responses

| Field | What it is |
| - | - |
| `id` | `resp_…`. |
| `conversation_id` | `conv_…`. |
| `status` | `in_progress`, `completed`, `failed`, `cancelled`, or `requires_action`: paused on a request in `output`, until it's decided (see [Approvals](#approvals)). |
| `error` | Why it failed, when `status` is `failed`. A failed turn still answers `200`: the conversation is fine, and you can send the next message. |
| `output` | The turn's [items](#items), in order: your message (and on a new conversation, the agent's greeting first), then every tool call and result, form, task and message the agent wrote. |
| `usage` | The tokens the turn's model calls used (`input_tokens`, `cached_tokens`, `cache_write_tokens`, `output_tokens`, `reasoning_tokens`) and `tasks`: each task it started, with its `cost_cents` once that's settled. `{}` until the turn ends. See [Billing](https://docs.guidinghand.ai/concepts/billing.md). |
| `created_at`, `ended_at` | When it started and ended. |

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. Without `stream`, 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:

```javascript theme={null}
let r = await gh('POST', '/v1/agents/support/responses', { conversation_id, input });
while (r.status === 'in_progress') r = await gh('GET', `/v1/responses/${r.id}?wait_ms=55000`);
const reply = r.output.filter((i) => i.type === 'assistant_message').at(-1)?.text;
```

A turn keeps running on Eager's side if your connection drops, and survives an Eager deploy: another server carries it on where it was. Fetch it by its id.

Within one turn the agent can make up to 20 model rounds and run up to 3 browser or computer tasks. A fourth task in the same turn is refused (the agent is told so and answers with what it has); a turn that needs more rounds ends `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](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events): each is `event: <type>` and `data: <JSON>` (the JSON has `type` too). A `: ping` comment comes every 15 seconds.

| Event | Data | When |
| - | - | - |
| `response.created` | `response` | First: the response, `in_progress`. Keep its `id`. |
| `output_text.delta` | `text` | The next piece of the agent's message, as it's written. |
| `output_text.reset` | | A model round failed and is being retried: discard what that round streamed since its first delta. |
| `message.delta` | `text` | One whole message from the agent, once written. |
| `tool_call.started` | `call_id`, `name`, `arguments` | The agent called a tool. |
| `tool_call.completed` | `call_id`, `name`, `output`, `label`, `error?`, `files?` | The tool's result. `label` says what it did in words ("Read /policies/returns.md"). |
| `ui` | `surface_id`, `title`, `fields`, `a2ui` | The agent showed a [form](#forms). |
| `message.view` | `seq`, `view` | The agent's answer (the message with that `seq`) was laid out as UI: [visual answers](#visual-answers). Just before `response.completed`. |
| `task.started` | `task_id`, `goal`, `kind` (`browser` or `desktop`), `computer?`, `plan?`, `session_id`, `live_url` | The agent started a [task](#tasks-from-a-conversation). `plan` is its [progress steps](#progress). `live_url` is its live view in the console (a path on guidinghand.ai). |
| `task.step` | `task_id`, `text`, `plan_step?` | What the task is doing now. With a plan, `plan_step` is the step it's on. |
| `task.completed` | `task_id`, `status`, `result` | The task ended. |
| `conversation.ended` | `reason`, `ended_at` | The agent [ended the conversation](#ending-a-conversation). Its goodbye may follow; then `response.completed`. |
| `input.delivered` | `seqs`, `to` (`reply` or `task`), `task_id?` | The agent read [messages sent while it worked](#messages-while-it-works) (their items' `seq`): the reply, before its next round, or the task it's running, at its next step. |
| `response.completed`, `response.failed`, `response.cancelled` | `response` | Last: the finished response, with all its items and usage. |
| `response.requires_action` | `response` | Last, instead: the reply paused on a request (an `approval` item in its `output`). See [Approvals](#approvals). |

```text theme={null}
event: response.created
data: {"type":"response.created","response":{"object":"response","id":"resp_k3Qm8ZpL2vXa","conversation_id":"conv_9qR2mW7cLp0e","status":"in_progress","output":[],"usage":{}}}

event: tool_call.started
data: {"type":"tool_call.started","call_id":"toolu_01B","name":"run_browser_task","arguments":{"goal":"Find the warranty end date for serial PF2ABCDE on support.example.com"}}

event: task.started
data: {"type":"task.started","task_id":"task_Xy4mQ0aL2pZk","goal":"Find the warranty end date for serial PF2ABCDE on support.example.com","kind":"browser","plan":["Looking up your device","Checking your warranty"],"session_id":"K7QP-2M4D","live_url":"/console/acme/tasks/task_Xy4mQ0aL2pZk"}

event: task.step
data: {"type":"task.step","task_id":"task_Xy4mQ0aL2pZk","text":"Opening the warranty lookup","plan_step":2}

event: task.completed
data: {"type":"task.completed","task_id":"task_Xy4mQ0aL2pZk","status":"completed","result":"The base warranty ends on 14 March 2027."}

event: tool_call.completed
data: {"type":"tool_call.completed","call_id":"toolu_01B","name":"run_browser_task","output":"{\"status\":\"completed\",\"result\":\"The base warranty ends on 14 March 2027.\",\"task_id\":\"task_Xy4mQ0aL2pZk\"}","label":"Ran a browser task: Find the warranty end date for serial PF2ABCDE on support.example.com"}

event: output_text.delta
data: {"type":"output_text.delta","text":"Your warranty runs until "}

event: output_text.delta
data: {"type":"output_text.delta","text":"14 March 2027."}

event: message.delta
data: {"type":"message.delta","text":"Your warranty runs until 14 March 2027."}

event: response.completed
data: {"type":"response.completed","response":{"object":"response","id":"resp_k3Qm8ZpL2vXa","status":"completed","output":[…],"usage":{…}}}
```

> [!WARNING]
>
> If Eager hands the turn to another server during a deploy, the stream can close without a `response.*` event. The turn carries on: follow it with `GET /v1/responses/{response_id}?wait_ms=55000`, using the id from `response.created`.

Errors found before the turn starts (a bad request, an empty balance, a busy conversation) come back as ordinary JSON errors, with their status, even with `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`:

| `type` | Fields | What it is |
| - | - | - |
| `user_message` | `text`, `files?`, `queued?` | A message from the person, with any attachments. A form's answers arrive as one too ("My answers to …"). `queued: true` while it was [sent during a reply](#messages-while-it-works) that hasn't read it yet. |
| `assistant_message` | `text`, `files?` | A message from the agent. The agent's greeting is the conversation's first item. |
| `tool_call` | `call_id`, `name`, `arguments`, `label` | The agent called a tool. |
| `tool_result` | `call_id`, `name`, `output`, `error?`, `files?` | What the tool returned, as text. `files` are files the agent's code made. |
| `task` | `task`: `task_id`, `kind`, `status`, `goal`, `result`, `computer?`, `plan?`, `plan_step?` | A browser or computer task: one the agent started, or one you started directly, in its own conversation. It's updated as the task runs, and is `paused` while it waits on a request; `plan` and `plan_step` are its [progress](#progress). |
| `approval` | `approval`: `approval_id`, `scope` (`run` or `step`), `action`, `risk`, `site`, `app`, `category`, `task_id`, `procedure_id`, `by` (`anyone` or `team`), `who`, `note`, `covers`, `surface_id`, `a2ui`, `decision` | A request for approval, in Eager's words. `decision` is `null` while it's open, then `{ decision, by, at, note? }`. See [Approvals](#approvals). |
| `question` | `question`: `question_id`, `task_id`, `question`, `options`, `surface_id`, `a2ui`, `answer` | A question a task asked. `answer` is `null` until it's answered, then `{ text, by, at }`. |
| `ui` | `surface_id`, `title`, `fields`, `a2ui`, `answer` | A form the agent showed, and its `answer` (`null` until answered). |
| `speech` | `speaker` (`caller` or `agent`), `text` | On a [phone call](https://docs.guidinghand.ai/guides/phone-numbers.md), a line said on the call, by the caller or the agent's voice, in order with the agent's work. It belongs to no turn (`response_id` is `null`). |

`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](https://docs.guidinghand.ai/concepts/tasks.md#questions-and-approvals) 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](https://docs.guidinghand.ai/concepts/computers.md#browsers-in-conversations), 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:

| Tool | What it does |
| - | - |
| `list_files`, `read_file`, `search_files` | Reads the agent's own [files](https://docs.guidinghand.ai/concepts/agents.md#the-file-system). In a conversation it sees PDFs and images too. |
| `read_procedure`, `find_procedure` | Reads the [procedure](https://docs.guidinghand.ai/concepts/procedures.md) for this kind of request. |
| Your HTTP tools | Calls your [functions](https://docs.guidinghand.ai/concepts/agents.md#http-tools), signed. In a conversation, the call's `task.task_id` is the response id. |
| `show_form` | Asks the person for details with a [form](#forms) instead of a list of questions. |
| `run_browser_task` | Runs a [task](#tasks-from-a-conversation) on the conversation's cloud browser. Only when the agent has a [browser](https://docs.guidinghand.ai/concepts/computers.md#browsers-in-conversations). |
| `run_desktop_task` | Runs a task on one of the agent's [computers](https://docs.guidinghand.ai/concepts/computers.md#linked-computers). Only when the agent has computers. |
| Code execution | With [Run code](https://docs.guidinghand.ai/concepts/agents.md#run-code), runs code in the conversation's own sandbox, its model provider's. |
| `end_conversation` | [Ends the conversation](#ending-a-conversation) once it's finished, with a short goodbye. |

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 its `end_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`](https://docs.guidinghand.ai/guides/webhooks#event-types) 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](https://docs.guidinghand.ai/guides/phone-numbers.md) 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: a `ui` 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](https://a2ui.org) 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:

```bash theme={null}
curl https://guidinghand.ai/v1/agents/support/responses \
  -H "Authorization: Bearer $EAGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "conv_9qR2mW7cLp0e",
    "action": { "name": "submit", "surfaceId": "srf_Lp0eQ2mW7cR9", "context": { "serial": "PF2ABCDE", "country": "United Kingdom" } }
  }'
```

* `choice` and `yes_no` take a string (`yes_no`: `"yes"` or `"no"`); with `allow_other`, send the typed answer as `"<id>_other"`. `multi_choice` takes a list. `number` and `slider` take a number within their range. `date` takes `YYYY-MM-DD`. `text` takes 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 (`409` after 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 `input` instead: 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: its `assistant_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.

```json theme={null}
"view": {
  "version": 1,
  "sections": [
    { "title": "Serial PF4A1QCG", "badge": { "text": "In warranty", "tone": "good" },
      "blocks": [{ "type": "facts", "items": [{ "label": "Ends", "value": "2027-02-27" }] }] },
    { "title": "Upgrade options",
      "blocks": [{ "type": "options", "items": [
        { "label": "Better", "title": "4Y Premier Support", "price": "£173.00", "detail": "5WS0W86716", "points": ["Next business day on site"], "pick": "I’ll go with 4Y Premier Support" }
      ] }] }
  ]
}
```

A section with a `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`](https://docs.guidinghand.ai/concepts/agents#tools): 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 `task` item in the conversation (and `task.*` events in a stream), and an ordinary [task](https://docs.guidinghand.ai/concepts/tasks.md): `GET /v1/tasks/{task_id}` has its events, result, recording and replay, and its `metadata` has the `conversation_id` and `response_id`.
* Watch it live: the console shows it playing inside the conversation, and `live_url` opens it.
* The person can still write while it runs: the task reads their [message at its next step](#messages-while-it-works).
* 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 has `plan` 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](https://docs.guidinghand.ai/guides/website-widget.md) 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](https://docs.guidinghand.ai/concepts/procedures.md#approval) whose Approval is on (also when the agent read it and starts the task without naming it), an [HTTP tool](https://docs.guidinghand.ai/concepts/agents.md#http-tools) with `approval: "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 an `approval` item in `output`: what will happen, in Eager's words, never the model's. A task waiting on it is `paused`.
* Your [webhook](https://docs.guidinghand.ai/guides/webhooks.md) gets `conversation.approval_required`.
* Nothing runs until it's decided. The person can decide it on the [website widget](https://docs.guidinghand.ai/guides/website-widget.md), 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.

```json theme={null}
{ "object": "conversation_item", "seq": 7, "response_id": "resp_k3Qm8ZpL2vXa", "type": "approval", "at": "2026-10-18T10:04:12.310Z",
  "approval": { "approval_id": "apr_lm3qpzkaw2kxr4tc5vne", "scope": "step", "action": "Click “Place order” in Chrome (shop.example.com)", "risk": "high", "task_id": "task_Xy4mQ0aL2pZk", "by": "anyone", "surface_id": "srf_q2mw7clp6eld4xk3vb6n", "a2ui": ["…"], "decision": null } }
```

Decide it with the call you send messages with: `conversation_id` and `approval`, and no `input`.

```bash theme={null}
curl https://guidinghand.ai/v1/agents/support/responses \
  -H "Authorization: Bearer $EAGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "conversation_id": "conv_9qR2mW7cLp0e", "approval": { "approval_id": "apr_lm3qpzkaw2kxr4tc5vne", "decision": "approve" } }'
```

The same reply carries on (same `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](https://docs.guidinghand.ai/concepts/tasks.md#questions-and-approvals) without an agent, whose conversation has none.

* **Who decided** is the decision's `by`: `team` (an API key's `key_id`, or a member's `email`), `person` (the person in the chat: a visitor on the website widget, or the customer at the computer, in the desktop app), `auto` (guardrails in `auto` mode), `refused` (`unattended` mode), `timeout` (nobody decided within the agent's `approval_timeout_minutes`) or `moved_on`. Each decision goes to the `conversation.approval_decided` webhook and the [audit log](https://docs.guidinghand.ai/concepts/audit-log.md), 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 is `by: "team"` too.
* **The first decision is used.** A later one gets `409` with `code: "already_decided"` and `decided_by`.
* **A new message instead** declines what's open (`moved_on`), ends the paused reply `cancelled`, 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 `auto` mode requests are approved without asking (and recorded); in `unattended` mode they're refused. See [Guardrails](https://docs.guidinghand.ai/concepts/guardrails.md).

## Attachments

People can send files with a message. Upload each one first, then send its id in `attachments`:

```bash theme={null}
# the file itself as the body
curl "https://guidinghand.ai/v1/uploads?filename=invoice.pdf" \
  -H "Authorization: Bearer $EAGER_API_KEY" \
  -H "Content-Type: application/pdf" \
  --data-binary @invoice.pdf

# or as JSON: { "filename": "invoice.pdf", "data": "<base64>" }
```

```json theme={null}
{ "object": "upload", "id": "upl_Wb1e3nV0qR7c", "filename": "invoice.pdf", "content_type": "application/pdf", "kind": "pdf", "bytes": 48213, "source": "person", "conversation_id": null, "created_at": "2026-10-18T10:02:11.000Z", "expires_at": "2026-10-19T10:02:11.000Z" }
```

```bash theme={null}
curl https://guidinghand.ai/v1/agents/support/responses \
  -H "Authorization: Bearer $EAGER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "conversation_id": "conv_9qR2mW7cLp0e", "input": "This is the invoice I mean.", "attachments": ["upl_Wb1e3nV0qR7c"] }'
```

* A file can be up to 10 MB (an image 5 MB). `kind` is `image` (PNG, JPEG, GIF, WebP), `pdf`, `text` (CSV, JSON, Markdown and other text) or `other`.
* Every model sees images and PDFs and reads text files. Other files, such as spreadsheets, need [Run code](https://docs.guidinghand.ai/concepts/agents.md#run-code) on a Claude or GPT model; sending one to an agent without it is a `400` with `code: "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}/content` the file, and `DELETE /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's `files`: download them with `/content`.

## Errors

| Status | When |
| - | - |
| `400` | A bad field, or an attachment the agent can't open (`attachment_not_supported`). |
| `413` | An upload over 10 MB (an image over 5 MB). |
| `402` | The balance can't pay for the reply: `insufficient_balance`, `auto_topup_limit_reached` or `card_needs_attention`. See [Billing](https://docs.guidinghand.ai/concepts/billing.md#when-the-balance-runs-out). |
| `404` | No such agent, conversation, version, upload, form or request. |
| `409` | A form was answered while a reply is still going, or already answered, someone decided the request first (`already_decided`), its task has ended (`not_pending`), the agent ended the conversation (`conversation_ended`), or an upload was sent in another conversation. |
| `429` | More than 300 uploads an hour from the org, or too many requests. |
| `503` | The agent's model isn't available right now. Try again, or choose another model for the agent. |

See [Errors](https://docs.guidinghand.ai/guides/errors.md) for the error shape.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.