---
title: Statistics API
slug: api/statistics-api
description: Introducing Sage: A Comprehensive Statistics Service for Kindly Chatbots

Learn about Sage, a standalone service exclusively designed to provide in-depth statistics for Kindly chatbots. Discover its dedicated non-personal data database, while ensuring use
icon: ▪️
docTags: 
createdAt: 2022-05-13T15:40:38.000Z
---

We have created a separate service for statistics about your Kindly chatbots. This service is named **Sage** and lives at `sage.kindly.ai`. The statistics you can view in the Kindly platform are provided by Sage. You can collect statistics for your own purposes from the same API that the platform uses.

Sage has its own database **which contains only non-personal data**. When end users chat with chatbots their messages are stored and processed in Kindly's database, and these can be manually or automatically deleted after some time to protect the end user's privacy.

The data in Sage's database will not be deleted, but this data doesn't contain the user's message or any personal metadata, only an indication that *someone* messaged the bot at a specific time. This way we can keep aggregated statistics such as the number of chats per day, while also allowing the end user to have their privacy protected.

# How to query

## Authentication

![](https://api.archbee.com/api/optimize/VLyTaamiYpVSyrmNspQvO/K1HCyQqv7T_L0AXafZpRQ_sage-auth2.png "Sage auth sequence diagram")

If you have the required permissions, you can create a **permanent** API key in the platform. This long-lived key can be used by a programmatic service you create to obtain short-lived JWTs which can then be used for statistics requests.

Send API key to **api**.kindly.ai to receive JWT:

```shell
curl --request GET \
  --url 'https://api.kindly.ai/api/v2/bot/{{BOT_ID}}/sage/auth' \
  --header 'authorization: Bearer {{API_KEY}}'
```

Send a request to **sage**.kindly.ai to test your new token

```shell
curl --request GET \
  --url 'https://sage.kindly.ai/api/v1/stats/bot/{{BOT_ID}}/sessions/messages?from=2019-01-01&to=2019-02-01' \
  --header 'authorization: Bearer {{JWT}}'
```

## From and to

The date filters `from` and `to` make a *half-closed interval*, meaning that everything on the `from`date is included in the results and everything on the `to` date is excluded.

Examples:

- To get data for the day of January 1st 2025, you would query with `{"from": "2025-01-01", "to": "2025-01-02"}`
- To get data for the entire year of 2024, you would query with `{"from": "2024-01-01", "to": "2025-01-01"}`

## Timezone

- [List of tz database time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)

All time values in the database are stored as [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). If you want results in your local timezone, which takes into consideration daylight saving time and other irregularities, you can provide a `tz` argument to your API request with the `tzdb` name of the timezone you want. It would look like this for Oslo:

`GET https://sage.kindly.ai/api/...&tz=Europe/Oslo`

The timezone parameter is important to consider if you are making a request where the data is aggregated by day. If someone messages the bot at 9 p.m. (summertime) on Monday in California, this is stored in the database as occurring at 4 a.m. on Tuesday. To get the API results scoped for California time, you would send a request with `tz=America/Los_Angeles`. In other words: *you must consider the location's timezone and adjust for it*.

## Granularity

Some aggregations can use a granularity parameter, for example, the aggregation that counts the number of messages per hour, day, or week. You can specify this with `granularity=hour`, etc. These default to `granularity=day` if not otherwise specified. Note that `hour` granularity is only allowed if the time between `from` and `to` is one week or less.

## Source and language

The events that are aggregated by Sage are labeled by which source and language the user and bot were chatting with. Your API requests can be filtered by these values. Not adding any filters to your request will include all data in the results, unfiltered.

### Source

Source is the location where your bot may be deployed. For example:

- `test` is source value for conversations by bot builders within the Kindly platform.
- `web` is the source value for Kindly Chat deployed on websites, excluding the Kindly platform.
- `app` is the source value for chats via the [Application API](docId\:CgAd_bxmm1-WVggzsMd1k).

If you do not filter by sources, all data will be included in the results, including `test` data.

To select only specific sources, you can send one or more filters like this:

`GET https://sage.kindly.ai/api/...&sources[]=web&sources[]=app`

You can also select sources to *exclude*, by prefixing with a minus sign. This means that the API will return data from every source except the ones listed.

`GET https://sage.kindly.ai/api/...&sources[]=-test`

### Language

The language used for chat between the user and the bot is represented by a two-letter language code. If your bot does not use multiple languages, you can safely ignore this parameter.

If you have multiple languages in your bot and you want to filter by language, you can send one or more filters like this:

`GET https://sage.kindly.ai/api/...&language_code[]=en&language_code[]=nb`

# API reference

Most of the APIs can be filtered by time, language, and source. To see what your options are for these filters, you can send a request to the `/meta/options` API.

::::CodeDrawer{title="Filters options - Example request " isResponseExpanded="true" autoGeneratedAnchorSlug="filters-options-example-request" legacyHash="Hatyt-oTDKX3KmaJTDsSX"}
:::CodeblockTabsExamples
```curl
curl --request GET \
  --url 'https://sage.kindly.ai/api/v1/stats/bot/{{BOT_ID}}/meta/options' \
  --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
  "data": {
    "first": "2019-04-28T07:22:52.825000",
    "last": "2019-09-04T06:48:41.100000",
    "language_codes": ["en", "nb"],
    "sources": ["app", "test", "web"],
    "granularities": ["hour", "day", "week"]
  }
}
```
:::
::::

## Chat sessions - time series

Number of sessions where users engaged with the bot

::::CodeDrawer{title="Chat sessions - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="chat-sessions-example-request" legacyHash="qFun0gVp-aI7o8hs6Kbtg"}
:::CodeblockTabsExamples
```curl
curl --request GET \
  --url 'https://sage.kindly.ai/api/v1/stats/bot/{{BOT_ID}}/sessions/chats?from=2019-07-01&to=2019-07-07' \
  --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
  "data": [
    {
      "count": 485,
      "date": "2019-07-01T00:00:00.000000"
    },
    {
      "count": 434,
      "date": "2019-07-02T00:00:00.000000"
    },
    {
      "count": 459,
      "date": "2019-07-03T00:00:00.000000"
    },
    {
      "count": 446,
      "date": "2019-07-04T00:00:00.000000"
    },
    {
      "count": 355,
      "date": "2019-07-05T00:00:00.000000"
    },
    {
      "count": 283,
      "date": "2019-07-06T00:00:00.000000"
    }
  ]
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: `UTC`)
- `granularity` (default: `day`)
- `sources[]`
- `language_codes[]`

## User messages - time series

Number of messages from users

::::CodeDrawer{title="User messages - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="user-messages-example-request" legacyHash="Mt3Nw17sNRSU42KIQMV-n"}
:::CodeblockTabsExamples
```curl
curl --request GET \
  --url 'https://sage.kindly.ai/api/v1/stats/bot/{{BOT_ID}}/sessions/messages?from=2019-07-01&to=2019-07-07' \
  --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
  "data": [
    {
      "count": 1661,
      "date": "2019-07-01T00:00:00.000000"
    },
    {
      "count": 1507,
      "date": "2019-07-02T00:00:00.000000"
    },
    {
      "count": 1563,
      "date": "2019-07-03T00:00:00.000000"
    },
    {
      "count": 1572,
      "date": "2019-07-04T00:00:00.000000"
    },
    {
      "count": 1227,
      "date": "2019-07-05T00:00:00.000000"
    },
    {
      "count": 964,
      "date": "2019-07-06T00:00:00.000000"
    }
  ]
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `granularity` (default: day)
- `sources[]`
- `language_codes[]`

## Fallback rate - time series

Number of- and fraction of bot replies that are fallbacks, as an aggregated time series.

`count` represents the number of fallback messages in the given time interval, `rate` represents which fraction of the total number of bot replies in the time interval are fallbacks.

::::CodeDrawer{title="Fallback rate time series - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="fallback-rate-time-series-example-request" legacyHash="zV_zDZ6dJYmBQqREBAmOM"}
:::CodeblockTabsExamples
```curl
curl --request GET \
  --url 'https://sage.kindly.ai/api/v1/stats/bot/{{BOT_ID}}/fallbacks/series?from=2019-07-01&to=2019-07-07' \
  --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
  "data": [
    {
      "count": 177,
      "date": "2019-07-01T00:00:00.000000",
      "rate": 0.12132805028009291
    },
    {
      "count": 161,
      "date": "2019-07-02T00:00:00.000000",
      "rate": 0.1207012308839985
    },
    {
      "count": 138,
      "date": "2019-07-03T00:00:00.000000",
      "rate": 0.09981955972573078
    },
    {
      "count": 149,
      "date": "2019-07-04T00:00:00.000000",
      "rate": 0.10669992872416251
    },
    {
      "count": 114,
      "date": "2019-07-05T00:00:00.000000",
      "rate": 0.10400945540503682
    },
    {
      "count": 94,
      "date": "2019-07-06T00:00:00.000000",
      "rate": 0.10891544117647059
    }
  ]
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `granularity` (default: day)
- `sources[]`
- `language_codes[]`

## Fallback rate - total

Number of- and fraction of bot replies that are fallbacks, as a total aggregate for the selected time interval.

`count` represents the number of fallback messages in the given time interval, `rate` represents which fraction of the total number of bot replies in the time interval are fallbacks.

::::CodeDrawer{title="Fallback rate - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="fallback-rate-example-request" legacyHash="0g5OpPqCvqNNMSCzZPbN1"}
:::CodeblockTabsExamples
```curl
curl --request GET \
  --url 'https://sage.kindly.ai/api/v1/stats/bot/{{BOT_ID}}/fallbacks/total?from=2019-07-01&to=2019-07-07' \
  --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
  "data": {
    "count": 836,
    "rate": 0.11061601724160727
  }
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `sources[]`
- `language_codes[]`

## Greeting engagement rate - time series

Shows the rate at which users engage with the bot (messaging or button clicking) after being shown a greeting message.

- `missed` shows the number of sessions where the bot greeted but the user didn't interact
- `rate` is the ratio of engaged chats out of total chats

::::CodeDrawer{title="Greeting engagement - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="greeting-engagement-example-request" legacyHash="0uuH4dMqCICDWrAeFwufB"}
:::CodeblockTabsExamples
```curl
curl --request GET \
  --url 'https://sage.kindly.ai/api/v2/stats/bot/{{BOT_ID}}/sessions/engagement?from=2025-01-01&to=2025-01-07' \
  --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
	"data": [
		{
			"date": "2025-01-01T00:00:00.000000",
			"missed": 199,
			"rate": 0.8224799286351472
		},
		{
			"date": "2025-01-02T00:00:00.000000",
			"missed": 163,
			"rate": 0.8560070671378092
		},
		{
			"date": "2025-01-03T00:00:00.000000",
			"missed": 216,
			"rate": 0.8210439105219552
		},
		{
			"date": "2025-01-04T00:00:00.000000",
			"missed": 163,
			"rate": 0.8454976303317535
		},
		{
			"date": "2025-01-05T00:00:00.000000",
			"missed": 138,
			"rate": 0.8411967779056386
		},
		{
			"date": "2025-01-06T00:00:00.000000",
			"missed": 212,
			"rate": 0.8302642113690952
		}
	]
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `granularity` (default: day)
- `sources[]`
- `language_codes[]`

## Engagement rate - total

Shows the rate at which users engage with the bot (messaging or button clicking) after being shown a greeting message.

::::CodeDrawer{title="Engagement - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="engagement-example-request" legacyHash="ONuiVSZHBOcSvYpXDqhVa"}
:::CodeblockTabsExamples
```curl
curl --request GET \
  --url 'https://sage.kindly.ai/api/v2/stats/bot/{{BOT_ID}}/sessions/engagement/total?from=2025-01-01&to=2025-01-07' \
  --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
	"data": {
		"missed": 1091,
		"rate": 0.8355193728328056
	}
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `sources[]`
- `language_codes[]`

## Web client / Kindly Chat pages - frequency table

Lists most frequent web pages where interactions with the bot have happened. Returns top 3 pages by default, use the `limit` parameter to request more results.

::::CodeDrawer{title="Kindly Chat pages - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="kindly-chat-pages-example-request" legacyHash="ba5eWZA0ajSDIZva0Obuj"}
:::CodeblockTabsExamples
```javascript
curl --request GET \
  --url 'https://sage.kindly.ai/api/v1/stats/bot/{{BOT_ID}}/chatbubble/pages?from=2019-07-01&to=2019-07-07' \
  --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
  "data": [
    {
      "messages": 1615,
      "sessions": 465,
      "web_host": "www.example.com",
      "web_path": "/"
    },
    {
      "messages": 1512,
      "sessions": 428,
      "web_host": "www.example.no",
      "web_path": "/"
    },
    {
      "messages": 784,
      "sessions": 264,
      "web_host": "www.example.com",
      "web_path": "/page"
    }
  ]
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `sources[]`
- `language_codes[]`
- `limit` (default: 3)

## Number of handovers - time series

The number of handover requests (while open), requests while closed, started handovers, and ended handovers in the requested time period, as a time series.

::::CodeDrawer{title="Handovers time series - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="handovers-time-series-example-request" legacyHash="f9JEJX0XgWijOXQTNSKeN"}
:::CodeblockTabsExamples
```curl
curl --request GET \
  --url 'https://sage.kindly.ai/api/v1/stats/bot/{{BOT_ID}}/takeovers/series?from=2019-09-16&to=2019-09-23' \
  --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
  "data": [
    {
      "date": "2019-09-16T00:00:00.000000",
      "ended": 40,
      "requests": 58,
      "requests_while_closed": 0,
      "started": 44
    },
    {
      "date": "2019-09-17T00:00:00.000000",
      "ended": 27,
      "requests": 40,
      "requests_while_closed": 0,
      "started": 28
    },
    {
      "date": "2019-09-18T00:00:00.000000",
      "ended": 35,
      "requests": 44,
      "requests_while_closed": 0,
      "started": 35
    },
    {
      "date": "2019-09-19T00:00:00.000000",
      "ended": 55,
      "requests": 88,
      "requests_while_closed": 0,
      "started": 55
    },
    {
      "date": "2019-09-20T00:00:00.000000",
      "ended": 64,
      "requests": 86,
      "requests_while_closed": 0,
      "started": 65
    },
    {
      "date": "2019-09-21T00:00:00.000000",
      "ended": 0,
      "requests": 0,
      "requests_while_closed": 2,
      "started": 0
    },
    {
      "date": "2019-09-22T00:00:00.000000",
      "ended": 0,
      "requests": 0,
      "requests_while_closed": 1,
      "started": 0
    }
  ]
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `granularity` (default: day)
- `sources[]`
- `language_codes[]`

## Number of handovers - total

The total number of handover requests (while open), requests while closed, started handovers, and ended handovers in the requested time period.

::::CodeDrawer{title="Handovers - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="handovers-example-request" legacyHash="t-Pa6WhbBQYT-vyOtD6Xn"}
:::CodeblockTabsExamples
```curl
curl --request GET \
  --url 'https://sage.kindly.ai/api/v1/stats/bot/{{BOT_ID}}/takeovers/totals?from=2019-09-16&to=2019-09-23' \
  --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
  "data": {
    "ended": 44,
    "requests": 79,
    "requests_while_closed": 3,
    "started": 47
  }
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `sources[]`
- `language_codes[]`

## Uncontained Sessions - time series

Provides a time series of chat sessions that required human intervention. An "uncontained session" is defined as a session where agent messages were sent, a handover occurred, or specific labeling actions were triggered. This endpoint returns the `n_uncontained_sessions` metric over a specified time period with configurable granularity.

::::CodeDrawer{title="Uncontained Sessions - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="uncontained-sessions-example-request" legacyHash="Y2RlcCs9i8eav4Qjr4UY1"}
:::CodeblockTabsExamples
```curl
curl --request GET \
     --url 'https://sage.kindly.ai/api/v2/stats/bot/{{BOT_ID}}/containment/uncontained_sessions?from=2025-02-01&to=2025-02-07' \
     --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
  "data": [
    {
      "count": 25,
      "date": "2025-02-01T00:00:00.000000"
    },
    {
      "count": 31,
      "date": "2025-02-02T00:00:00.000000"
    },
    {
      "count": 22,
      "date": "2025-02-03T00:00:00.000000"
    },
    {
      "count": 28,
      "date": "2025-02-04T00:00:00.000000"
    },
    {
      "count": 19,
      "date": "2025-02-05T00:00:00.000000"
    },
    {
      "count": 35,
      "date": "2025-02-06T00:00:00.000000"
    }
  ]
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `granularity` (default: day)
- `sources[]`
- `language_codes[]`

## Uncontained Sessions - total

Provides the total count of chat sessions that required human intervention for the selected time interval. An "uncontained session" is one where agent messages were sent, a handover occurred, or specific labeling actions were triggered.

::::CodeDrawer{title="Uncontained Sessions Total - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="uncontained-sessions-total-example-request" legacyHash="4gV0P696TM1tmQ4fiuDzJ"}
:::CodeblockTabsExamples
```curl
curl --request GET \
     --url 'https://sage.kindly.ai/api/v2/stats/bot/{{BOT_ID}}/containment/uncontained_sessions/total?from=2025-02-01&to=2025-02-07' \
     --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
  "data": {
    "count": 160
  }
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `sources[]`
- `language_codes[]`

## Button clicks - frequency table

An ordered list of the most clicked buttons in the chatbot, and the dialogues the buttons belong to. Has both a `count` of the number of occurrences and a `ratio` of the total amount of button clicks for the period. Returns top 5 results by default. Use the `limit` parameter to request more data.

::::CodeDrawer{title="Button clicks - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="button-clicks-example-request" legacyHash="7e4nckmtfeTdjPGPNZshx"}
:::CodeblockTabsExamples
```curl
curl --request GET \
  --url 'https://sage.kindly.ai/api/v1/stats/bot/{{BOT_ID}}/buttons/most_clicked?from=2019-09-16&to=2019-09-23' \
  --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
  "data": [
    {
      "button_id": "9d09d898-90d5-4ba9-879c-7faa0f3ec629",
      "button_type": "quick_reply",
      "count": 46,
      "dialogue_id": "6a6de8f8-724d-4dc3-adcb-0b30373cfb3b",
      "ratio": 0.12234042553191489
    },
    {
      "button_id": "6b0c5cc7-d612-4f1e-bfd9-6057130146f3",
      "button_type": "dialogue_trigger",
      "count": 26,
      "dialogue_id": "6a6de8f8-724d-4dc3-adcb-0b30373cfb3b",
      "ratio": 0.06914893617021277
    },
    {
      "button_id": "a75a41b7-0132-4c0c-9cf7-82cb500c8af9",
      "button_type": "dialogue_trigger",
      "count": 23,
      "dialogue_id": "6a6de8f8-724d-4dc3-adcb-0b30373cfb3b",
      "ratio": 0.061170212765957445
    },
    {
      "button_id": "17b666ac-0276-478b-a626-8659a90a1a25",
      "button_type": "quick_reply",
      "count": 20,
      "dialogue_id": "c4aaf069-a0b0-4eb1-ba65-81f46f8c8ecc",
      "ratio": 0.05319148936170213
    },
    {
      "button_id": "4f85b25b-b793-4d20-9479-3c2c5b224ebf",
      "button_type": "dialogue_trigger",
      "count": 17,
      "dialogue_id": "72f96a8f-4aaf-4ac2-bb73-1cbec8f2aa57",
      "ratio": 0.04521276595744681
    }
  ]
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `sources[]`
- `language_codes[]`
- `limit` (default: 5)

## Labels - frequency table

The most frequent labels added to chats. Returns top 5 labels by default. Use the `limit` parameter to request more data.

::::CodeDrawer{title="Labels - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="labels-example-request" legacyHash="kKgHTuaRDb2DOSVk3C5RY"}
:::CodeblockTabsExamples
```curl
curl --request GET \
  --url 'https://sage.kindly.ai/api/v1/stats/bot/{{BOT_ID}}/chatlabels/added?from=2019-09-16&to=2019-09-23' \
  --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
  "data": [
    {
      "count": 5,
      "label_id": "35bf04c0-392d-46a0-a741-4d660134d461",
      "label_text": "Some label"
    },
    {
      "count": 2,
      "label_id": "50e57dca-9045-46bf-93d4-55de77795444",
      "label_text": "Another label"
    },
    {
      "count": 2,
      "label_id": "2e08978f-24f0-4207-97c0-8945e19b751e",
      "label_text": "Label 3"
    }
  ]
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `sources[]`
- `language_codes[]`
- `limit` (default: 5)

## Customer feedback - frequency table

Shows the number of feedbacks in each category for the given time period.

:::hint{type="info"}
Since there are two categories of feedback in Kindly, there are two URLs for this API, both with the same parameters:

- `api/v2/stats/bot/{{BOT_ID}}/feedback/bot` for metrics about feedback for bot conversations
- `api/v2/stats/bot/{{BOT_ID}}/feedback/handover` for metrics about feedback for handover conversations (i.e. with humans)
:::

- `count` shows the number of times a particular rating has been given
- `index` is a representation of the ordering of the feedback options shown to the user. 0 is the leftmost option, etc. Most rating systems order the values left to right from worst to best, but this is customizable.
- `rating` is the text description of the given feedback
- `rating_system` is the type of feedback shown
- `ratio` is the number of feedbacks given of this rating out of the total number of feedbacks given

::::CodeDrawer{title="Customer feedback - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="customer-feedback-example-request" legacyHash="jN_7Tvjj85ETeDY4cZdzB"}
:::CodeblockTabsExamples
```curl
# For bot feedbacks
curl --request GET \
  --url 'https://sage.kindly.ai/api/v2/stats/bot/{{BOT_ID}}/feedback/bot?from=2025-01-01&to=2025-01-07' \
  --header 'Authorization: Basic {{JWT}}'

# For handover feedbacks
curl --request GET \
  --url 'https://sage.kindly.ai/api/v2/stats/bot/{{BOT_ID}}/feedback/handover?from=2025-01-01&to=2025-01-07' \
  --header 'Authorization: Basic {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
	"data": [
		{
			"count": 5,
			"index": 0,
			"rating": "Bad",
			"rating_system": 5,
			"ratio": 0.45454545454545453
		},
		{
			"count": 4,
			"index": 2,
			"rating": "Okay",
			"rating_system": 5,
			"ratio": 0.36363636363636365
		},
		{
			"count": 2,
			"index": 4,
			"rating": "Great",
			"rating_system": 5,
			"ratio": 0.18181818181818182
		}
	]
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `sources[]`
- `language_codes[]`

# Example

This example requires Python 3.6 or higher (uses [f-strings](https://www.python.org/dev/peps/pep-0498/)) and the package [requests](https://pypi.org/project/requests/).

You will need to provide two values yourself, the relevant bot ID and an API key with "read statistics" permissions that you have created at `https://app.kindly.ai/bot/BOT_ID/connect/api-keys`. The code exchanges the API key for a JWT, uses the JWT to get data about the number of messages per day for the bot's lifetime, then renders the data as a Unicode box-character bar chart.

```python
from time import sleep
from urllib.parse import urljoin

import requests

BOT_ID: int = REPLACE ME
KINDLY_STATISTICS_API_KEY: str = REPLACE ME  # get this from https://app.kindly.ai/workspace/BOT_ID/connect/api-keys
SAGE_API_ROOT = 'https://sage.kindly.ai/api/v1/'

JWT = None


def get_new_jwt():
    url = f'https://api.kindly.ai/api/v2/bot/{BOT_ID}/sage/auth'
    response = requests.get(url, headers={'Authorization': f'Bearer {KINDLY_STATISTICS_API_KEY}'})
    response.raise_for_status()
    return response.json()['jwt']


def sage_request(endpoint, params=None):
    global JWT

    url = urljoin(SAGE_API_ROOT, f"stats/bot/{BOT_ID}/{endpoint.lstrip('/')}")
    while True:
        if JWT is None:
            JWT = get_new_jwt()

        try:
            response = requests.get(url, params, headers={'Authorization': f'Bearer {JWT}'})
            response.raise_for_status()
        except requests.HTTPError as e:
            if e.response.status_code == 401:
                print('401: fetching new JWT before retrying')
                JWT = None
                continue

            if e.response.status_code == 429:
                wait_time = e.response.headers.get('retry-after', 10)
                print(f'429: rate limited: waiting {wait_time}s before retrying')
                sleep(wait_time)
                continue

            raise

        return response.json()['data']


# First, fetch some metadata about the bot
options = sage_request('/meta/options')

first = options['first']
last = options['last']
granularities = options['granularities']
language_codes = options['language_codes']
sources = options['sources']

# Display the metadata
print(
    f"""
The earliest date with data is: {first}
The latest date with data is: {last}
Granularity options are: {granularities}
Language options are: {language_codes}
Source options are: {sources}
"""
)

# Then, fetch data about the number of messages in the time period
messages = sage_request('/sessions/messages', {'from': first, 'to': last})

highest = max(x['count'] for x in messages)
if highest == 0:
    raise Exception('No data found')


# Display the data about the number of messages in the terminal
def bar_chart(label, n, scale=1):
    """
    make a "sideways bar chart" with Unicode box characters
    """
    width = n // scale
    if n == 0:
        bar = ''
    elif width == 0:
        bar = '▍'
    else:
        bar = '█' * width

    return f"{label}: {bar} {n}"


LINE_WIDTH = 80
c = highest // LINE_WIDTH
print(f'Number of messages between {first} and {last}')
print(*(bar_chart(x['date'].split('T', 2)[0], x['count'], scale=c) for x in messages), sep='\n')

```

# Legacy APIs

These APIs are for deprecated features that may be removed in the future, but as long as they remain compatible we keep documentation here.

## Chat bubble feedback

If you have enabled the feedback feature in Kindly Chat you can get a summary of the ratings given by users in the period.
The API considers the ratings as numbers, with 1 being the lowest rating.
Both the `count` of the number of ratings and the `ratio` of the total number of ratings is given.

This API only returns aggregated data.
If you want to analyze the feedback texts users have given you can get these from an inbox backup.
See also: [How to make an Insights Backup](docId\:rT_9Y4iR9pE18M9lvMUWr).

::::CodeDrawer{title="(Legacy) Feedback - Example request" isResponseExpanded="true" autoGeneratedAnchorSlug="legacy-feedback-example-request" legacyHash="gY7dKmmTvvEKmnXi_55jR"}
:::CodeblockTabsExamples
```curl
curl --request GET \
  --url 'https://sage.kindly.ai/api/v1/stats/bot/{{BOT_ID}}/feedback/summary?from=2019-09-16&to=2019-09-23' \
  --header 'authorization: Bearer {{JWT}}'
```
:::

:::CodeblockTabsResponses
```javascript
{
  "data": {
    "binary": [
      {
        "count": 50,
        "rating": 1,
        "ratio": 0.5
      },
      {
        "count": 50,
        "rating": 2,
        "ratio": 0.5
      }
    ],
    "emojis": [
      {
        "count": 0,
        "rating": 1,
        "ratio": 0.0
      },
      {
        "count": 0,
        "rating": 2,
        "ratio": 0.0
      },
      {
        "count": 0,
        "rating": 3,
        "ratio": 0.0
      }
    ]
  }
}
```
:::
::::

### Required parameters

- `from` (YYYY-MM-DD)
- `to` (YYYY-MM-DD)

### Optional parameters

- `tz` (default: UTC)
- `sources[]`
- `language_codes[]`

