# Get conversation time by ids

> GET /v1/conversations/times/{ids} · Core resources API

Source: https://developer.kustomer.com/api/core-resources/get-conversation-time-by-ids

```http
GET https://{orgname}.api.kustomerapp.com/v1/conversations/times/{ids}
Authorization: Bearer <API key>
```

## Authorization

Requires any one of these roles: `org.permission.conversation.read`; or legacy `org.user.conversation.read`. The API key or user must hold at least one of them. Legacy roles predate permission sets and still work.

Retrieves `conversation_time`s base on the unique ID or the IDs of `conversation_time`(s).

## About Conversation time

Conversation time records how long agents actively spent working on a conversation in Kustomer. Each record is one handle session, and the records together are the source for handle time reporting.

While an agent has a conversation open in the agent desktop, Kustomer times how long they work on it. Each stretch of work is saved as a `conversation_time` record linked to the [conversation](https://developer.kustomer.com/api/core-resources#conversations), its customer and the agent who did the work. One conversation collects many records over its life, across agents and separate sessions of work. Kustomer creates these records itself; the API only reads them. Use them to calculate handle time per conversation, per agent or per team in your own reporting.

#### What a record holds

`handleAt` is when the session started and `handleTime` is how long it lasted. `messageAt` and `messageTime` cover the part of it the agent spent composing replies. Each record also captures the context at that moment: `channels` of the conversation, its `assignedUsers` and `assignedTeams`, and `createdByTeams`, the agent's teams.

#### v1 and v2

Kustomer has two handle timers, and each API version reads one of them.

| | `GET /v1/conversations/{id}/times` | `GET /v2/conversations/{id}/times` |
| --- | --- | --- |
| Time unit | Seconds | Milliseconds |
| Records returned | The conversation's records | Only records from the v2 timer (`timerVersion: "v2"`), filtered by your org's handle time settings |
| Extra detail | None | Who handled it (`handleBy`), pauses, browser focus, the action that triggered the record, and what changed |

The v2 filtering follows your org's average handle time settings: how focus is handled, which conversation statuses are excluded, and whether time without an assigned agent counts. v2 records also say which agent action triggered the capture, in `agentActionTrigger`: `reply_sent`, `note_sent`, `conversation_done`, `conversation_reopened`, `conversation_snoozed` or `kobject_status_change`. `isFocused` tells you whether the browser tab was in focus. `touched`, `resolved`, `snoozed` and `reopened` show what happened to the conversation during the session, and `messagesSent`, `notesSent`, `noteAt` and `noteTime` break down the agent's activity.

#### Which endpoint to use

| To | Call |
| --- | --- |
| Get handle time for a conversation, with full detail, matching your org's settings | [`GET /v2/conversations/{id}/times`](https://developer.kustomer.com/api/core-resources-v2/get-conversation-times-by-conversation-v2) |
| Get all handle time records for a conversation, in seconds | [`GET /v1/conversations/{id}/times`](https://developer.kustomer.com/api/core-resources/get-conversation-times-by-conversation) |
| Fetch specific records by ID | [`GET /v1/conversations/times/{ids}`](https://developer.kustomer.com/api/core-resources/get-conversation-time-by-ids), with a comma-separated list |

Don't mix units: convert v1 seconds or v2 milliseconds before you add them together. Both list endpoints return the newest records first and are paginated with `page` and `pageSize`; pass `sort=asc` for oldest first. Reading conversation time needs conversation read access.

Related: [Conversations](https://developer.kustomer.com/api/core-resources#conversations), [Work sessions](https://developer.kustomer.com/api/queues-and-routing#work-sessions), [Users](https://developer.kustomer.com/api/access-management#users)

### Recipes

#### 1. Break a conversation's handle time down by agent

See how much time each agent spent on one conversation, for example when reviewing an escalation that passed through several hands.

**Step 1.** [`GET /v2/conversations/{id}/times`](https://developer.kustomer.com/api/core-resources-v2/get-conversation-times-by-conversation-v2.md). Page through the conversation's v2 records. These follow your org's handle time settings, and `handleTime` is in milliseconds. Each record's `handleBy` is the agent.

```bash title="cURL"
curl "https://api.kustomerapp.com/v2/conversations/$CONVERSATION_ID/times?pageSize=100" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v2/conversations/$CONVERSATION_ID/times --all --output jsonl > times.jsonl
```

**Step 2.** Group by `handleBy` and add up `handleTime`. Convert to minutes at the end, not per record, so rounding doesn't build up.

```bash title="kusty"
jq -s 'group_by(.data.attributes.handleBy)
  | map({agent: .[0].data.attributes.handleBy, minutes: (map(.data.attributes.handleTime) | add / 60000), sessions: length})' times.jsonl
```

**Step 3.** [`GET /v1/users/{ids}`](https://developer.kustomer.com/api/access-management/get-multiple-users-by-ids.md). Records carry user IDs only. Fetch all the agents in one call with their IDs comma-separated.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/users/$USER_IDS" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/users/$USER_IDS | jq -r '.data[] | [.id, .attributes.displayName] | @tsv'
```

> `GET /v1/conversations/{id}/times` returns the conversation's records without your org's handle time filtering, with `handleTime` in seconds and no `handleBy`. Don't add its numbers to v2 ones without converting.

#### 2. Total handle time for the conversations closed yesterday

Build a daily handle time figure in your own warehouse from the conversations agents finished each day.

**Step 1.** [`POST /v1/customers/search`](https://developer.kustomer.com/api/core-resources/create-customer-search.md). Search for conversations last marked done yesterday. Local dates are read in `timeZone`, and `lte` on a local date runs to the end of that day, so `gte` and `lte` on the same date cover the whole day.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/customers/search?pageSize=100" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"queryContext": "conversation", "timeZone": "America/New_York", "and": [
    {"conversation_last_done_created_at": {"gte": "2026-10-20"}},
    {"conversation_last_done_created_at": {"lte": "2026-10-20"}}]}'
```

```bash title="kusty"
kusty api POST /v1/customers/search --all --fields id --data '{"queryContext": "conversation", "timeZone": "America/New_York", "and": [
    {"conversation_last_done_created_at": {"gte": "2026-10-20"}},
    {"conversation_last_done_created_at": {"lte": "2026-10-20"}}]}' \
  | jq -r '.data.id' > conversations.txt
```

**Step 2.** [`GET /v2/conversations/{id}/times`](https://developer.kustomer.com/api/core-resources-v2/get-conversation-times-by-conversation-v2.md). For each conversation, page through its v2 records and add up `handleTime` (milliseconds). The total covers the conversation's whole life, including work before yesterday; filter on `handleAt` if you want only yesterday's sessions.

```bash title="cURL"
curl "https://api.kustomerapp.com/v2/conversations/$CONVERSATION_ID/times?pageSize=100" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
while read -r id; do
  kusty api GET /v2/conversations/$id/times --all \
    | jq -s --arg id "$id" '{conversation: $id, handleMs: (map(.data.attributes.handleTime) | add // 0)}'
done < conversations.txt
```

> That is one request per conversation, so pace the loop to stay inside your [rate limits](https://developer.kustomer.com/docs/concepts/rate-limits). Search only covers conversations updated in the past two years.

### Example object

A real record from [`GET /v2/conversations/{id}/times`](https://developer.kustomer.com/api/core-resources-v2/get-conversation-times-by-conversation-v2.md):

```json
{
  "type": "conversation_time",
  "id": "6476d23b4f1a9e001b8c3d21",
  "attributes": {
    "createdAt": "2024-05-30T14:22:35.187Z",
    "createdByTeams": [
      "5d8bb1995886ef001aee07d3"
    ],
    "assignedUsers": [
      "5ce4672fcce83d001a9e83e8"
    ],
    "assignedTeams": [],
    "handleAt": "2024-05-30T14:22:20.053Z",
    "handleTime": 15134,
    "handleBy": "5ce4672fcce83d001a9e83e8",
    "timerVersion": "v2",
    "isFocused": true,
    "isInitialFocusSession": true,
    "agentActionTrigger": "reply_sent",
    "conversationStatus": "open",
    "touched": true,
    "resolved": false,
    "snoozed": false,
    "reopened": false,
    "messageAt": "2024-05-30T14:22:22.100Z",
    "messageTime": 8430,
    "messagesSent": 1,
    "channels": [
      "chat"
    ]
  },
  "relationships": {
    "org": {
      "links": {
        "self": "/v1/orgs/58ff8ca3ce53ff1000a47865"
      },
      "data": {
        "type": "org",
        "id": "58ff8ca3ce53ff1000a47865"
      }
    },
    "customer": {
      "links": {
        "self": "/v1/customers/5f184e74d3bc42001946cdb9"
      },
      "data": {
        "type": "customer",
        "id": "5f184e74d3bc42001946cdb9"
      }
    },
    "conversation": {
      "links": {
        "self": "/v1/conversations/5f184e74c61792001af42e49"
      },
      "data": {
        "type": "conversation",
        "id": "5f184e74c61792001af42e49"
      }
    },
    "createdBy": {
      "links": {
        "self": "/v1/users/5ce4672fcce83d001a9e83e8"
      },
      "data": {
        "type": "user",
        "id": "5ce4672fcce83d001a9e83e8"
      }
    },
    "handleBy": {
      "links": {
        "self": "/v1/users/5ce4672fcce83d001a9e83e8"
      },
      "data": {
        "type": "user",
        "id": "5ce4672fcce83d001a9e83e8"
      }
    }
  },
  "links": {
    "self": "/v1/conversations/times/6476d23b4f1a9e001b8c3d21"
  }
}
```

Fields:

- `attributes.createdAt` (string): When Kustomer saved the record, at the end of the handle session. List endpoints sort by it, newest first.
- `attributes.createdByTeams` (array): The teams of the agent who did the work, as team IDs. Use it to total handle time by team.
- `attributes.assignedUsers` (array): The users assigned to the conversation when the record was captured, as user IDs.
- `attributes.assignedTeams` (array): The teams assigned to the conversation when the record was captured, as team IDs.
- `attributes.handleAt` (string): When the handle session started.
- `attributes.handleTime` (integer): How long the session lasted. Milliseconds on `/v2`, seconds on `/v1`, so convert before adding the two together.
- `attributes.handleBy` (string): The ID of the agent whose action triggered the record. Only `/v2` records have it.
- `attributes.timerVersion` (string): Which handle timer captured the record. `/v2` returns only `v2` records.
- `attributes.isFocused` (boolean): Whether the agent's browser tab was in focus during this segment. Your org's handle time settings decide whether unfocused time counts.
- `attributes.isInitialFocusSession` (boolean): `true` until the tab first loses focus; `false` for segments that resumed after it came back.
- `attributes.agentActionTrigger` (string): The agent action that triggered the record's capture, such as `reply_sent` or `conversation_done`.
- `attributes.conversationStatus` (string): The conversation's status, such as `open` or `done`, when the record was captured.
- `attributes.touched` (boolean): Whether the conversation was updated during the session.
- `attributes.resolved` (boolean): Whether the conversation was resolved during the session.
- `attributes.snoozed` (boolean): Whether the conversation was snoozed during the session.
- `attributes.reopened` (boolean): Whether the conversation was reopened during the session.
- `attributes.messageAt` (string): When the agent first started composing a message in this session.
- `attributes.messageTime` (integer): Time spent composing messages, part of `handleTime`. Milliseconds on `/v2`.
- `attributes.messagesSent` (integer): Messages the agent sent during the session.
- `attributes.channels` (array): The conversation's channels, such as `chat` or `email`, when the record was captured.
- `relationships.customer` (object): The customer the conversation is with.
- `relationships.conversation` (object): The conversation the time was spent on. Most conversations collect several records.
- `relationships.handleBy` (object): The agent whose action triggered the record, the same user as `attributes.handleBy`.

### Relationships

- Points to **Customers** via `relationships.customer`: The customer whose conversation this conversation time record measures.
- Points to **Conversations** via `relationships.conversation`: The conversation an agent's handle time was recorded on. A conversation collects many conversation time records over its life, across agents and sessions.
- Points to **Users** via `relationships.handleBy`: The agent whose action captured this conversation time record. Only records from the v2 timer have it.

### Permissions

Fewest roles for a read-only integration: `org.permission.conversation.read`. For every endpoint: `org.permission.conversation.read`.

## Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `ids` | string | yes | Comma-separated resource IDs. |

## Responses

### 200

Success

- `data` (object, required)
  - `type` (string, required)
  - `id` (string, required)
  - `attributes` (object, required)
    - `createdAt` (string, required)
    - `createdByTeams` (array<string>, required)
    - `assignedUsers` (array<string>, required)
    - `assignedTeams` (array<any>, required)
    - `handleAt` (string, required)
    - `handleTime` (integer, required): Duration the conversation was in an active handle session, in seconds.
    - `channels` (array<string>, required)
  - `relationships` (object, required)
    - `org` (object, required)
    - `customer` (object, required)
    - `conversation` (object, required)
    - `createdBy` (object, required)
  - `links` (object, required)
    - `self` (string, required)

```json
{
  "data": {
    "type": "conversation_time",
    "id": "5faee8bd01b6a7060833dafb",
    "attributes": {
      "createdAt": "2020-11-13T20:12:45.025Z",
      "createdByTeams": [
        "5d8bb1995886ef001aee07d3"
      ],
      "assignedUsers": [
        "5ce4672fcce83d001a9e83e8"
      ],
      "assignedTeams": [],
      "handleAt": "2020-11-13T20:12:26.299Z",
      "handleTime": 19,
      "channels": [
        "chat"
      ]
    },
    "relationships": {
      "org": {
        "links": {
          "self": "/v1/orgs/58ff8ca3ce53ff1000a47865"
        },
        "data": {
          "type": "org",
          "id": "58ff8ca3ce53ff1000a47865"
        }
      },
      "customer": {
        "links": {
          "self": "/v1/customers/5f184e74d3bc42001946cdb9"
        },
        "data": {
          "type": "customer",
          "id": "5f184e74d3bc42001946cdb9"
        }
      },
      "conversation": {
        "links": {
          "self": "/v1/conversations/5f184e74c61792001af42e49"
        },
        "data": {
          "type": "conversation",
          "id": "5f184e74c61792001af42e49"
        }
      },
      "createdBy": {
        "links": {
          "self": "/v1/users/5ce4672fcce83d001a9e83e8"
        },
        "data": {
          "type": "user",
          "id": "5ce4672fcce83d001a9e83e8"
        }
      }
    },
    "links": {
      "self": "/v1/conversations/times/5faee8bd01b6a7060833dafb"
    }
  }
}
```

### 400

Bad Request

### 401

Unauthorized

### 403

Forbidden

### 404

Not Found

## Example request

```bash
curl --request GET \
  --url 'https://api.kustomerapp.com/v1/conversations/times/60254e3ec413ae00571e3c96' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```
