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

# List conversations

> Read all Fini conversations for your workspace, sorted newest first, with cursor pagination.

Reads conversations for the workspace tied to your API key. Use it to export conversations into your own systems for analytics, QA review, or downstream processing. This is the read path described on the [API overview](/en/api-reference/overview) — it pulls data *out* of Fini and does not change agent behavior. Results are sorted by latest message time, newest first.

<Tip>
  Use [List agents](/en/api-reference/list-agents) to look up the `botId` values accepted by this endpoint's optional agent filter. If you already know a conversation ID, use [Get conversation](/en/api-reference/get-conversation). If you need to send a new turn into Fini, use [Generate Answer](/en/api-reference/generate-answer). This page is export-only.
</Tip>

## Headers

<ParamField header="Authorization" type="string" required>
  Bearer token containing your Fini workspace API key. Format: `Bearer fini_...`
</ParamField>

## Query parameters

<ParamField query="since" type="integer">
  Start of the time window in Unix epoch milliseconds. If omitted, Fini defaults to the last 7 days.
</ParamField>

<ParamField query="until" type="integer">
  End of the time window in Unix epoch milliseconds. If omitted, Fini defaults to the current time.
</ParamField>

<ParamField query="limit" type="integer" default="50">
  Maximum number of conversations to return. Minimum `1`, maximum `100`.
</ParamField>

<ParamField query="cursor" type="string">
  Conversation ID to paginate from. Pass the `nextCursor` or `prevCursor` value returned by the previous response.
</ParamField>

<ParamField query="direction" type="string" default="next">
  Pagination direction when a cursor is supplied. **Note the inverted mapping:** `next` moves to *older* conversations; `previous` moves back toward *newer* ones. This is because results are sorted newest first, so "next page" goes further back in time.
</ParamField>

<ParamField query="botId" type="string">
  Optional agent ID filter. When provided, only conversations for that agent are returned.
</ParamField>

<ParamField query="source" type="array">
  Optional comma-separated conversation sources. Common values include `api`, `widget`, `ui`, `standalone`, `testsuite`, `replay`, `zendesk`, `intercom`, `front`, `hubspot`, `salesforce`, `gorgias`, `livechat`, `slack`, `discord`, `freshdesk`, and `freshchat`.
</ParamField>

<ParamField query="channel" type="array">
  Optional comma-separated channel filter.
</ParamField>

<Warning>
  The `since` / `until` window cannot exceed **90 days**, and `since` must be strictly earlier than `until`. A request where `since == until` is rejected for the same reason and returns `400 Bad Request`.
</Warning>

<Note>
  Additional behavior worth knowing:

  * The endpoint only returns conversations where **Fini has touched the conversation**.
  * `source` and `channel` accept either comma-separated values or repeated query params if your HTTP client sends arrays.
</Note>

<RequestExample>
  ```bash cURL theme={null}
  curl --request GET \
    --url 'https://api-prod.usefini.com/v2/hc-interactions/public?limit=25&channel=chat&source=widget,ui' \
    --header 'Authorization: Bearer fini_your_api_key'
  ```

  ```python Python theme={null}
  import requests

  response = requests.get(
      "https://api-prod.usefini.com/v2/hc-interactions/public",
      headers={"Authorization": "Bearer fini_your_api_key"},
      params={
          "limit": 25,
          "channel": "chat",
          "source": "widget,ui",
      },
  )
  data = response.json()
  ```

  ```javascript Node.js theme={null}
  const params = new URLSearchParams({
    limit: "25",
    channel: "chat",
    source: "widget,ui",
  });

  const response = await fetch(
    `https://api-prod.usefini.com/v2/hc-interactions/public?${params.toString()}`,
    {
      headers: {
        Authorization: "Bearer fini_your_api_key",
      },
    }
  );
  const data = await response.json();
  ```
</RequestExample>

## Response

<ResponseField name="interactions" type="array">
  Array of `PublicConversation` objects returned for the requested window and filters. See [PublicConversation](#nested-objects).
</ResponseField>

<ResponseField name="hasMore" type="boolean">
  Whether more results exist beyond the current page.
</ResponseField>

<ResponseField name="nextCursor" type="string | null">
  Cursor to use when paginating forward. `null` if there is no next page.
</ResponseField>

<ResponseField name="prevCursor" type="string | null">
  Cursor to use when paginating backward. `null` if there is no previous page.
</ResponseField>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "interactions": [
      {
        "id": "0b8626b0-4cc8-4a3d-8fc2-f18ad1a4a1a8",
        "createdAt": 1747075200000,
        "source": "widget",
        "channel": "chat",
        "status": "Escalated to Human Agent",
        "externalId": null,
        "url": "https://app.usefini.com/dashboard/inbox/0b8626b0-4cc8-4a3d-8fc2-f18ad1a4a1a8",
        "subjectPreview": "How do I cancel my plan?",
        "hasFeedback": true,
        "resolved": true,
        "userAttributes": {
          "plan": "Pro",
          "country": "US"
        },
        "usedArticles": [
          {
            "id": "7f5392e5-dc7d-4558-8860-cf3ea4b32f94",
            "title": "Canceling your subscription",
            "documentUrl": null
          }
        ],
        "usedSubfolders": [
          {
            "id": "2f4df245-b2a1-47f1-bb21-398c8ab00b56",
            "title": "Billing"
          }
        ],
        "events": [
          {
            "id": "2d3d5f6d-0fbc-4a6d-874d-4e2c632f124a",
            "createdAt": 1747075201000,
            "role": "user",
            "type": "message",
            "message": "How do I cancel my plan?",
            "externalId": null,
            "externalCreatedAt": null,
            "csatRating": null,
            "feedback": null,
            "approved": null,
            "resolved": null,
            "attachments": [],
            "tags": [],
            "usedArticles": []
          },
          {
            "id": "f61a9a11-2c3b-4704-8f57-7078854d87cf",
            "createdAt": 1747075205000,
            "role": "finibot",
            "type": "message",
            "message": "You can cancel from Billing Settings in your account.",
            "externalId": null,
            "externalCreatedAt": null,
            "csatRating": 5,
            "feedback": "Solved my question instantly.",
            "approved": true,
            "resolved": null,
            "attachments": [],
            "tags": [],
            "usedArticles": [
              {
                "id": "7f5392e5-dc7d-4558-8860-cf3ea4b32f94",
                "title": "Canceling your subscription",
                "documentUrl": null
              }
            ]
          }
        ]
      }
    ],
    "hasMore": false,
    "nextCursor": null,
    "prevCursor": null
  }
  ```

  ```json 400 Bad Request theme={null}
  {
    "statusCode": 400,
    "message": "Invalid query parameters",
    "error": "Bad Request"
  }
  ```

  ```json 401 Unauthorized theme={null}
  {
    "statusCode": 401,
    "message": "Invalid or revoked API key",
    "error": "Unauthorized"
  }
  ```
</ResponseExample>

<Note>
  The `400 Bad Request` `message` varies by cause — it may quote the specific rule that failed (`since`/`until` range > 90 days, `since >= until`, invalid UUID, etc.). See [Errors](#errors) for the full list of causes.
</Note>

## Field semantics

### userAttributes

`userAttributes` is an open-ended object. Fini returns the attributes captured on the conversation as-is, so the schema is **consumer-defined** and can vary by workspace. If your bots populate CRM-specific or workflow-specific fields, they appear here unchanged.

### csatRating

`events[].csatRating` is passed through from the stored event data:

* `null` means no CSAT value is present on that event
* numeric values are returned as stored

If your upstream data writes `0`, the API returns `0`. Do **not** automatically treat `0` as "unrated" unless that is how your own channel or integration encodes the value.

### Event roles

`events[].role` can currently be:

| Value      | Meaning                                                          |
| ---------- | ---------------------------------------------------------------- |
| `user`     | A message from the end user or customer.                         |
| `agent`    | A human agent message synced from the connected provider.        |
| `finibot`  | A Fini-generated message or system action.                       |
| `otherbot` | A non-Fini bot or automation message from the upstream provider. |

### Event types

`events[].type` can currently be:

| Value               | Meaning                                                         |
| ------------------- | --------------------------------------------------------------- |
| `message`           | A normal message event.                                         |
| `internalnote`      | A private/internal note rather than a customer-visible message. |
| `no_reply`          | Fini decided not to send a reply.                               |
| `silent_escalation` | Fini escalated without posting a visible reply.                 |
| `debounce`          | A debounce/system event used internally around message timing.  |
| `widget_form`       | A widget form conversation event.                               |

## Nested objects

<AccordionGroup>
  <Accordion title="PublicConversation" icon="comments">
    | Field            | Type                | Description                                                                            |
    | ---------------- | ------------------- | -------------------------------------------------------------------------------------- |
    | `id`             | string              | Conversation ID. Also used as the pagination cursor.                                   |
    | `createdAt`      | epoch ms            | When the conversation was created.                                                     |
    | `source`         | string              | Source of the conversation, such as `api`, `widget`, `ui`, or a connected integration. |
    | `channel`        | string              | Channel type. Currently `email` or `chat`.                                             |
    | `status`         | string \| null      | Human-readable conversation status, if available.                                      |
    | `externalId`     | string \| null      | External system identifier when the conversation came from an integration.             |
    | `url`            | string \| null      | Link back to the source conversation, when available.                                  |
    | `subjectPreview` | string \| null      | Short subject or preview string for the conversation.                                  |
    | `hasFeedback`    | boolean             | Whether the conversation has feedback attached.                                        |
    | `resolved`       | boolean \| null     | Whether the conversation has been marked resolved.                                     |
    | `userAttributes` | object \| null      | User attributes captured on the conversation. See [Field semantics](#userattributes).  |
    | `usedArticles`   | `PublicArticle[]`   | Public article references used during the conversation.                                |
    | `usedSubfolders` | `PublicSubfolder[]` | Public subfolder references used during the conversation.                              |
    | `events`         | `PublicEvent[]`     | Chronological event stream for the conversation.                                       |
  </Accordion>

  <Accordion title="PublicEvent" icon="message">
    | Field               | Type              | Description                                                                                                                                                                   |
    | ------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `id`                | string            | Event ID.                                                                                                                                                                     |
    | `createdAt`         | epoch ms          | When the event was created.                                                                                                                                                   |
    | `role`              | string            | One of the event roles documented above.                                                                                                                                      |
    | `type`              | string            | One of the event types documented above.                                                                                                                                      |
    | `message`           | string \| null    | Message text, when the event carries message content.                                                                                                                         |
    | `externalId`        | string \| null    | Provider-side message ID when available.                                                                                                                                      |
    | `externalCreatedAt` | epoch ms \| null  | Provider-side timestamp when available.                                                                                                                                       |
    | `csatRating`        | number \| null    | Numeric CSAT value if one was stored on the event.                                                                                                                            |
    | `feedback`          | string \| null    | Free-text feedback note attached to the event.                                                                                                                                |
    | `approved`          | boolean \| null   | Thumbs up (`true`), thumbs down (`false`), or unrated (`null`).                                                                                                               |
    | `resolved`          | boolean \| null   | Resolution flag for negatively rated or flagged events.                                                                                                                       |
    | `attachments`       | array             | File attachments on the event. Passed through from stored event data; commonly includes fields like `originalUrl`, `contentType`, `expiresAt`, `sizeBytes`, and storage URLs. |
    | `tags`              | `PublicTag[]`     | Tags attached to the event.                                                                                                                                                   |
    | `usedArticles`      | `PublicArticle[]` | Articles retrieved for that specific event.                                                                                                                                   |
  </Accordion>

  <Accordion title="PublicArticle" icon="book-open">
    | Field         | Type           | Description                         |
    | ------------- | -------------- | ----------------------------------- |
    | `id`          | string         | Article ID.                         |
    | `title`       | string         | Article title.                      |
    | `documentUrl` | string \| null | Source document URL when available. |
  </Accordion>

  <Accordion title="PublicSubfolder" icon="folder">
    | Field   | Type   | Description   |
    | ------- | ------ | ------------- |
    | `id`    | string | Folder ID.    |
    | `title` | string | Folder title. |
  </Accordion>

  <Accordion title="PublicTag" icon="tag">
    | Field     | Type           | Description                          |
    | --------- | -------------- | ------------------------------------ |
    | `id`      | string         | Tag ID.                              |
    | `name`    | string         | Tag name.                            |
    | `groupId` | string \| null | Parent tag-group ID when one exists. |
  </Accordion>
</AccordionGroup>

## Pagination

Cursor pagination is relative to the current cursor, not to time:

* pass `nextCursor` with `direction=next` to move to **older** conversations
* pass `prevCursor` with `direction=previous` to move back toward **newer** conversations

If you omit `cursor`, the API starts from the newest matching conversations in the requested time window.

## Errors

<AccordionGroup>
  <Accordion title="400 Bad Request" icon="circle-exclamation">
    The query parameters are invalid. Common causes: an invalid UUID, `since` later than or equal to `until`, or a time window larger than 90 days. The response `message` quotes the specific rule that failed.
  </Accordion>

  <Accordion title="401 Unauthorized" icon="lock">
    The API key is missing, malformed, revoked, or invalid. Confirm you are sending `Authorization: Bearer fini_...` with the full key.
  </Accordion>

  <Accordion title="403 Forbidden" icon="shield-halved">
    The API key does not include the `read` scope required for this route.
  </Accordion>

  <Accordion title="429 Too Many Requests" icon="gauge-high">
    You exceeded the rate limit. Back off and retry with your own client-side policy — see [Rate limits](#rate-limits).
  </Accordion>

  <Accordion title="500 Internal Server Error" icon="triangle-exclamation">
    Fini failed to fulfill the request. Retry once, then contact support if the error persists.
  </Accordion>
</AccordionGroup>

## Rate limits

The API applies a global throttle of **100 requests per 60 seconds**.

Two caveats:

* this endpoint does not currently document `X-RateLimit-*` headers
* this endpoint does not currently document a `Retry-After` header contract

If you receive `429`, back off and retry with your own client-side policy.
