# Conversations

> List, count, read and delete the conversations of the API key's user.

A conversation is the stored history of a chat: the messages exchanged with a model or
an [assistant](assistants.md), its name, and a few links (assistant, organization, task,
workflow run). Conversations are created by the [chat](chat.md) endpoint; there is no
endpoint to create or edit one directly. To continue a conversation, send its `id` as
`conversation_id` to [chat](chat.md).

All four endpoints see only the conversations **of the API key's user**:

- conversations the user started in the app or through the API, in personal use and in
  any organization they belong to;
- conversations started on the user's behalf by scheduled tasks and workflow runs.

Conversations of other users are never returned, even when they belong to the same
organization or are shared publicly by a link. A conversation that exists but is not
yours is reported as not found.

The cost of a conversation is available from the [usage cost endpoint](account.md).

## List conversations

| | |
|---|---|
| Endpoint | `POST /api/v3/conversation/find` |
| Scope | `ayeto.conversation` |
| Rate limit | [default](conventions.md#rate-limits) |

Returns the user's conversations that match the filters, newest first unless you sort
otherwise. Each conversation is returned with its full message history, so always page
through the results.

### Request

The body is a query object with the usual [filters, sorting and paging](conventions.md#filters)
fields. Send `{}` to get everything; the body itself is required.

| Field | Type | Required | Description |
|---|---|---|---|
| `filters` | array | No | Filter conditions, see [filters](conventions.md#filters) and the [examples](#useful-filters) below. Several conditions are combined with AND. |
| `sort_key` | string | No | Field to sort by, for example `created.timestamp`, `updated.timestamp`, `accessed.timestamp` or `name`. Default: `created.timestamp`, descending. |
| `sort_order` | integer | No | `0` ascending, `1` descending. Default `0` when `sort_key` is set. |
| `limit_from` | integer | No | Index of the first result (zero-based). Without it, all matching conversations are returned. |
| `limit_to` | integer | No | Index after the last result (exclusive), not a page size: `limit_from: 20, limit_to: 40` returns the results at positions 20 to 39 (counting from 0). |

To read everything, request pages until a page comes back shorter than requested. The
total for a pager comes from [count conversations](#count-conversations).

### Useful filters

Filters can use any field of the [conversation](#conversation), including fields of the
messages (`messages.content`, `messages.role`, `messages.model`). Message conditions
are checked against every stored message, including tool results and other messages
that are not returned in `messages`.

| Goal | Filter |
|---|---|
| Conversations with one assistant | `["assistant_id", "==", "3f6c1a2e-8b4d-4e7a-9c1f-2d5e6a7b8c90"]` |
| Personal conversations only (no organization) | `["organization_id", "==", null]` |
| Conversations in one organization | `["organization_id", "==", "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"]` |
| Conversations you had yourself, without task and workflow runs | `["task_id", "==", null]`, `["task_execution_id", "==", null]`, `["workflow_run_id", "==", null]` |
| Conversations created by one workflow | `["workflow_id", "==", "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a"]` |
| Changed since a moment (ms since epoch) | `["updated.timestamp", ">=", 1759363200000]` |
| Name contains a word (case-insensitive) | `["name", "regex", "invoice"]` |
| Any message contains a phrase (case-insensitive) | `["messages.content", "regex", "delivery date"]` |
| Started with one of several models | `["model", "in", ["gpt-5", "claude-sonnet-4-5"]]` |
| Any answer written by a model | `["messages.model", "==", "gpt-5"]` |

Text filters are matched against the stored text after the API escapes `<`, `>`, `"`
and `'`, so a filter value containing these characters rarely matches; see
[filters](conventions.md#filters).

### Response

`200 OK` with an array of [conversations](#conversation).

### Errors

| Status | `detail` | Cause |
|---|---|---|
| `403` | `permission denied` | The user's account is not allowed to read conversations. |
| `422` | validation error | The body is missing, or a filter or `sort_key` is invalid. |

Authentication, rate limit and server errors are described in
[conventions](conventions.md#errors).

### Example

The 20 most recently updated conversations with one assistant:

```bash
curl -X POST "https://ayeto.ai/api/v3/conversation/find" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": [["assistant_id", "==", "3f6c1a2e-8b4d-4e7a-9c1f-2d5e6a7b8c90"]],
    "sort_key": "updated.timestamp",
    "sort_order": 1,
    "limit_from": 0,
    "limit_to": 20
  }'
```

```json
[
  {
    "id": "8e2f4a6c-1b3d-4e5f-8a7b-9c0d1e2f3a4b",
    "name": "Delivery terms for order 1042",
    "model": "gpt-5",
    "summary": "",
    "organization_id": null,
    "assistant_id": "3f6c1a2e-8b4d-4e7a-9c1f-2d5e6a7b8c90",
    "task_id": null,
    "task_execution_id": null,
    "workflow_id": null,
    "workflow_run_id": null,
    "public_sharing": false,
    "messages": [
      {
        "id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
        "timestamp": 1759395601000,
        "role": "user",
        "content": "What delivery date did we agree for order 1042?",
        "reasoning_content": null,
        "model": null,
        "attachments": [],
        "tool_runs": {}
      },
      {
        "id": "1b2c3d4e-5f6a-4b7c-9d8e-0f1a2b3c4d5e",
        "timestamp": 1759395606000,
        "role": "assistant",
        "content": "The agreed delivery date for order 1042 is 14 October 2026.",
        "reasoning_content": null,
        "model": "gpt-5",
        "attachments": [],
        "tool_runs": {}
      }
    ],
    "created": {"timestamp": 1759395600000, "user_id": "6c5b4a39-2817-4f6e-9d5c-4b3a29180f7e"},
    "updated": {"timestamp": 1759395606000, "user_id": "6c5b4a39-2817-4f6e-9d5c-4b3a29180f7e"},
    "accessed": {"timestamp": 1759395606000, "user_id": "6c5b4a39-2817-4f6e-9d5c-4b3a29180f7e"}
  }
]
```

## Count conversations

| | |
|---|---|
| Endpoint | `POST /api/v3/conversation/count` |
| Scope | `ayeto.conversation` |
| Rate limit | [default](conventions.md#rate-limits) |

Returns the number of the user's conversations that match the filters, see
[counting](conventions.md#counting).

### Request

The same query object as [list conversations](#list-conversations); send `{}` to count
all of them. Only `filters` is used: `limit_from`, `limit_to` and the sort fields are
ignored, so you can send the same body as for the page you display.

### Response

`200 OK` with the number of matching conversations as a JSON integer.

```json
42
```

### Errors

| Status | `detail` | Cause |
|---|---|---|
| `403` | `permission denied` | The user's account is not allowed to read conversations. |
| `422` | validation error | The body is missing, or a filter or `sort_key` is invalid. |

Authentication, rate limit and server errors are described in
[conventions](conventions.md#errors).

### Example

Conversations with one assistant changed since a moment:

```bash
curl -X POST "https://ayeto.ai/api/v3/conversation/count" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": [
      ["assistant_id", "==", "3f6c1a2e-8b4d-4e7a-9c1f-2d5e6a7b8c90"],
      ["updated.timestamp", ">=", 1759363200000]
    ]
  }'
```

```json
7
```

## Get a conversation

| | |
|---|---|
| Endpoint | `POST /api/v3/conversation/get` |
| Scope | `ayeto.conversation` |
| Rate limit | [default](conventions.md#rate-limits) |

Returns one conversation with its full message history.

### Request

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | UUID | Yes | Id of the conversation. |

The id can also be sent as the `entity_id` query parameter instead, with no body or
`{}`; see [parameters](conventions.md#parameters).

Reading a conversation counts as opening it: its `accessed` timestamp is updated (at
most once a minute).

### Response

`200 OK` with a [conversation](#conversation).

### Errors

| Status | `detail` | Cause |
|---|---|---|
| `403` | `permission denied` | The user's account is not allowed to read conversations. |
| `404` | `entity not found, id: <id>` | No conversation with this id exists. |
| `404` | `entity not found` | The conversation exists but belongs to another user. |
| `422` | validation error | The id is missing or not a UUID, or `id` and `entity_id` differ. |

### Example

```bash
curl -X POST "https://ayeto.ai/api/v3/conversation/get" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "8e2f4a6c-1b3d-4e5f-8a7b-9c0d1e2f3a4b"}'
```

The response is a single object in the same shape as one item of the
[list example](#example).

## Delete a conversation

| | |
|---|---|
| Endpoint | `POST /api/v3/conversation/delete` |
| Scope | `ayeto.conversation` |
| Rate limit | [default](conventions.md#rate-limits) |

Deletes a conversation permanently, together with the files that belong to it
(attachments sent in it and images or other files generated in it). This cannot be
undone.

### Request

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | UUID | Yes | Id of the conversation. |

The id can also be sent as the `entity_id` query parameter instead, with no body or
`{}`; see [parameters](conventions.md#parameters).

### Response

`200 OK` with the id of the deleted conversation as a JSON string.

```json
"8e2f4a6c-1b3d-4e5f-8a7b-9c0d1e2f3a4b"
```

### Errors

| Status | `detail` | Cause |
|---|---|---|
| `403` | `permission denied` | The user's account is not allowed to delete conversations. |
| `404` | `entity not found, id: <id>` | No conversation with this id exists. |
| `404` | `entity not found` | The conversation exists but belongs to another user. |
| `422` | validation error | The id is missing or not a UUID, or `id` and `entity_id` differ. |
| `500` | `can not delete entity` | The conversation could not be deleted; retry later. |

### Example

```bash
curl -X POST "https://ayeto.ai/api/v3/conversation/delete" \
  -H "uni-api-key: $AYETO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "8e2f4a6c-1b3d-4e5f-8a7b-9c0d1e2f3a4b"}'
```

## Conversation

The object returned by all conversation endpoints.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | UUID | Yes | Conversation id. The same value is the `conversation_id` of [chat](chat.md). |
| `name` | string | Yes | Display name. `New conversation` until a name is suggested after the first answer or set in the app. |
| `model` | string | Yes | Id of the model the conversation was started with (see [models](models-and-tools.md)). Individual answers may come from another model; see the message `model`. |
| `summary` | string | Yes | Summary generated in the app on request; empty string when none was generated. |
| `organization_id` | UUID or null | No | Organization the conversation belongs to; `null` for personal use. |
| `assistant_id` | UUID or null | No | [Assistant](assistants.md) the conversation is held with; `null` when chatting directly with a model. |
| `task_id` | UUID or null | No | Scheduled task that created the conversation. |
| `task_execution_id` | UUID or null | No | Run of that task. |
| `workflow_id` | UUID or null | No | [Workflow](workflows.md) whose assistant step created the conversation. |
| `workflow_run_id` | UUID or null | No | Run of that workflow. |
| `public_sharing` | boolean | Yes | `true` when the user has shared the conversation publicly by a link in the app. |
| `messages` | array of [Message](#message) | Yes | User and assistant messages in chronological order. System, tool and other internal messages are left out. |
| `created`, `updated`, `accessed` | object | Yes | `{timestamp, user_id}` metadata, see [common fields](conventions.md#common-fields). `updated` changes with every new message; `accessed` is when the conversation was last opened. |

### Message

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | UUID | Yes | Message id. |
| `timestamp` | timestamp | Yes | When the message was created (ms since epoch). |
| `role` | string | Yes | `user` or `assistant`. |
| `content` | string or null | No | Message text in Markdown. In assistant messages it also contains the [tool calls](#tool-calls-in-message-content) made while answering. |
| `reasoning_content` | string or null | No | The model's reasoning text, when the model returns it. |
| `model` | string or null | No | Model that wrote an assistant message; `null` on user messages. |
| `attachments` | array of [Attachment](#attachment) | Yes | Files attached to the message. |
| `tool_runs` | object | Yes | Answers of sub-assistants called during this message, see [tool calls](#tool-calls-in-message-content). Usually empty. |

### Attachment

| Field | Type | Required | Description |
|---|---|---|---|
| `filename` | string | Yes | File name. |
| `content_type` | string | Yes | MIME type, for example `image/png` or `application/pdf`. |
| `file_url` | string | Yes | Public URL of the file. Files sent or generated in the conversation are deleted with it. |
| `name` | string or null | No | Display name, when different from `filename`. |
| `description` | string or null | No | Short description of the file. |

### Tool calls in message content

Every tool the model called while writing an assistant message appears in `content` as
a block that starts with `"\n\n### calling AI tool ...\n"` and ends with `"\n***\n"`.
The text between the two markers is the tool's output shown to the user:

```json
{
  "role": "assistant",
  "content": "Let me look that up.\n\n### calling AI tool ...\nSearching the web for \"order 1042 delivery\"...\n***\nThe agreed delivery date is 14 October 2026."
}
```

Remove these blocks if you only need the answer text. (The [chat](chat.md) endpoint can
leave them out of its own response with `remove_tool_calls`; the stored conversation always keeps them.)

When an assistant calls another assistant as a tool, that assistant's answer is stored
in `tool_runs`, keyed by the position of the tool call in `content` as a string (`"0"`
for the first tool call, `"1"` for the second, and so on). Each value has a `content`
string in the same format and its own nested `tool_runs`.
