# Analytics REST API

The **Analytics REST API** complements the [GraphQL API](/integrating-with-our-platform-api.md) by exposing a small set of pre-aggregated, cursor-paginated endpoints that return time-series data for the most common Workspace KPIs — chats, messages, returning users, CSAT, AI quality and technical errors.

Use it when you need to:

* Build a **custom dashboard** in your BI tool (Looker, Metabase, Power BI, Grafana, etc.).
* Feed an **internal data warehouse** with daily Workspace metrics.
* Generate **periodic reports** (weekly/monthly) without re-aggregating raw chat data.

{% hint style="info" %}
The Analytics REST API serves the same numbers shown in the **Analytics** section of the platform UI, computed from a TimescaleDB continuous aggregate. Today's value is approximate until end-of-day; historical days are stable.
{% endhint %}

## Base URL

All Analytics endpoints are served from `https://platform.indigo.ai`. The exact path depends on the endpoint — see the **Endpoints** section below.

Each request is scoped to a single workspace via the `{project_id}` segment in the URL.

## Authentication

Authentication is handled via a **Personal Access Token (PAT)**, the same credential used by the GraphQL API. Pass it in the `Authorization` header of every request:

```http
Authorization: Bearer pat-*your_pat_value*
```

The token must belong to the same workspace identified by `{project_id}` in the path; otherwise the platform returns `403 Unauthorized`.

{% hint style="warning" %}
API access is a premium feature and incurs an additional cost. If you don't have a PAT yet, [contact us](/need-help/our-customer-success-team.md).
{% endhint %}

## Pagination

All daily endpoints use **opaque cursor-based pagination**. Each response includes a `metadata` object with:

* `next_cursor` — Base64-encoded cursor to pass as `cursor` (or `after`) on the next request. `null` when there are no more pages.
* `total_count` / `total_days` / `days_count` — total number of days in the requested range.

To fetch the next page, repeat the request with `?cursor=<next_cursor>` (or `?after=<next_cursor>` for `users/unique-daily`).

The `limit` query parameter controls page size: default `30` (or `31` for `users/unique-daily`), max `365`.

## Common parameters

| Parameter          | Type            | Required | Description                                                  |
| ------------------ | --------------- | -------- | ------------------------------------------------------------ |
| `from`             | `YYYY-MM-DD`    | ✅        | Start of the date range (inclusive).                         |
| `to`               | `YYYY-MM-DD`    | ✅        | End of the date range (exclusive).                           |
| `limit`            | integer (1–365) | ❌        | Days per page. Default `30` (`31` for `users/unique-daily`). |
| `cursor` / `after` | string          | ❌        | Pagination cursor returned by the previous response.         |

A `400 ERROR` response with body `{"status": "ERROR", "message": "Missing required params: from, to"}` is returned when `from` or `to` are missing.

## Rate limiting

The Analytics REST API is rate-limited to **100 requests per 60 seconds** per Personal Access Token. When the limit is exceeded the API returns `429 Too Many Requests`:

```json
{
  "status": "error",
  "message": "rate limit exceeded",
  "retry_after": 60
}
```

The `retry_after` value is the number of seconds to wait before retrying.

***

## Endpoints

Six endpoints cover the headline Workspace KPIs. All return the standard envelope:

```json
{
  "status": "SUCCESS",
  "data": [ /* array of daily buckets */ ],
  "metadata": { "total_count": 90, "next_cursor": "..." }
}
```

### 📨 Daily messages

Returns daily inbound/outbound message counts plus feedback and human-takeover signals.

```http
GET /rest/analytics/{project_id}/messages/daily?from=2026-01-01&to=2026-02-01
```

**Example response**

```json
{
  "status": "SUCCESS",
  "data": [
    {
      "date": "2026-01-01",
      "inbound_messages": 1164,
      "outbound_messages": 1902,
      "positive_feedback": 12,
      "negative_feedback": 3,
      "ht_requests": 5,
      "ht_starts": 3
    }
  ],
  "metadata": {
    "total_days": 31,
    "next_cursor": null
  }
}
```

* `inbound_messages` — messages sent by the user to the agent.
* `outbound_messages` — messages sent by the agent to the user.
* `positive_feedback` / `negative_feedback` — thumb-up / thumb-down counts.
* `ht_requests` — number of Handover Block triggers (requests for a human agent).
* `ht_starts` — number of human takeover sessions actually started.

### 💬 Daily chats

Returns the number of chat sessions started each day. Historical days are served from the `chat_aggregates` hypertable; today's value is computed live and may be partial.

```http
GET /rest/dashboard/{project_id}/chats/count?from=2026-01-01&to=2026-02-01
```

**Example response**

```json
{
  "status": "SUCCESS",
  "data": [
    { "date": "2026-01-15", "count": 142 }
  ],
  "metadata": {
    "total_count": 31,
    "next_cursor": "MjAyNi0wMS0zMVQwMDowMDowMFo="
  }
}
```

### 👥 Returning users (daily)

Returns the daily count of distinct users who already existed before that day and were active on it — a proxy for **engagement of returning users**. Counts are based on HyperLogLog cardinality (\~2% error margin).

```http
GET /rest/dashboard/{project_id}/users/unique-daily?from=2026-01-01&to=2026-02-01
```

This endpoint accepts `after` instead of `cursor` for pagination.

**Example response**

```json
{
  "status": "SUCCESS",
  "data": [
    { "date": "2026-01-01T00:00:00Z", "unique_users": 42 }
  ],
  "metadata": {
    "total_count": 31,
    "next_cursor": "MjAyNi0wMS0xNVQwMDowMDowMFo="
  }
}
```

### 😊 CSAT scores (daily)

Returns the daily aggregated CSAT score from in-chat surveys. Days with no responses are included with `total = 0` and `avg_score = null`.

```http
GET /rest/dashboard/{project_id}/csat/daily?from=2026-01-01&to=2026-02-01
```

**Example response**

```json
{
  "status": "SUCCESS",
  "data": [
    { "date": "2026-01-15", "total": 12, "avg_score": 4.2 }
  ],
  "metadata": {
    "days_count": 31,
    "next_cursor": null
  }
}
```

* `total` — number of CSAT responses on this day.
* `avg_score` — weighted average score on a 1–5 scale, or `null` when `total = 0`.

### 🤖 AI quality (daily)

Returns the daily aggregated AI Quality score, the platform's automatic measure of how well the agent answered (combines grounding, relevance, and resolution signals).

```http
GET /rest/dashboard/{project_id}/ai_quality/daily?from=2026-01-01&to=2026-02-01
```

**Example response**

```json
{
  "status": "SUCCESS",
  "data": [
    { "date": "2026-01-15", "score": 0.87, "sample_size": 240 }
  ],
  "metadata": {
    "total_days": 31,
    "next_cursor": null
  }
}
```

### ⚠️ Technical errors (daily)

Returns the daily count of `api_error` events — failures emitted by API Blocks, integrations, or LLM calls. Days with no errors are zero-filled.

```http
GET /rest/dashboard/{project_id}/errors/daily?from=2026-01-01&to=2026-02-01
```

**Example response**

```json
{
  "status": "SUCCESS",
  "data": [
    {
      "date": "2026-01-01",
      "bucket": "2026-01-01T00:00:00Z",
      "error_count": 12
    },
    {
      "date": "2026-01-02",
      "bucket": "2026-01-02T00:00:00Z",
      "error_count": 0
    }
  ],
  "metadata": {
    "total_count": 31,
    "next_cursor": "MjAyNi0wMS0zMVQwMDowMDowMFo="
  }
}
```

***

## Error responses

| Status | Body                                                                       | When                                          |
| ------ | -------------------------------------------------------------------------- | --------------------------------------------- |
| `400`  | `{"status": "ERROR", "message": "Missing required params: from, to"}`      | `from` or `to` not provided.                  |
| `400`  | `{"status": "ERROR", "message": "Invalid cursor"}`                         | Cursor is malformed or expired.               |
| `401`  | `{"status": false, "message": "unauthenticated"}`                          | Missing or invalid PAT.                       |
| `403`  | `{"status": false, "message": "unauthorized"}`                             | PAT does not match `{project_id}`.            |
| `429`  | `{"status": "error", "message": "rate limit exceeded", "retry_after": 60}` | Rate limit hit (100 req/60s).                 |
| `500`  | `{"status": "ERROR", "message": "Internal server error"}`                  | Unexpected server error — retry with backoff. |

## Best practices

* **Page through everything**: even ranges with many empty days return zero-filled buckets, so always loop until `next_cursor` is `null`.
* **Cache aggressively for historical days**: values for past days are stable and safe to cache for hours or days. Only re-fetch the current day.
* **Stay within the rate limit**: batch requests by widening the date range and using `limit` rather than firing one request per day.
* **Pair with GraphQL when you need raw data**: the REST endpoints return aggregates only. To drill into individual conversations or messages, use the [`conversations` and `messages` GraphQL queries](/integrating-with-our-platform-api.md).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://guide.indigo.ai/integrating-with-our-platform-api/analytics-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
