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

# Analytics

> Workspace analytics endpoint — reached, sent, replied, reply %, positive %, filterable by campaign, sender line, user, and date window.

## Overview

`GET /api/analytics` returns one consolidated metric block for your workspace,
filterable down to a single campaign, sender line, sending user, or date
window. The same numbers power the **/analytics** page in the dashboard and
the `analytics` MCP tool, so a UI chart, an MCP question, and a curl call all
agree.

<Info>
  Reply rate and positive rate are computed as **unique-by-contact** ratios.
  A contact who got 5 messages still counts as 1 toward "reached", and one
  contact who replies 10 times still counts as 1 toward "uniqueReplied".
</Info>

***

## Endpoint

```
GET https://app.tuco.ai/api/analytics
```

### Authentication

Bearer-token. Pass your API key (`tuco_...`) in the `Authorization` header:

```bash theme={null}
curl -H "Authorization: Bearer tuco_..." https://app.tuco.ai/api/analytics
```

### Query parameters

<ParamField query="campaignId" type="string">
  Restrict the result to one campaign (MongoDB ObjectId as 24-char hex).
</ParamField>

<ParamField query="lineId" type="string">
  Restrict to a single sender line (MongoDB ObjectId as 24-char hex).
</ParamField>

<ParamField query="userId" type="string">
  Restrict to a single Clerk user ID who created the messages (`user_…`).
</ParamField>

<ParamField query="dateFrom" type="string">
  Inclusive lower bound, ISO 8601 (e.g. `2026-06-01T00:00:00Z`). Filters on
  `message.createdAt`.
</ParamField>

<ParamField query="dateTo" type="string">
  Inclusive upper bound, ISO 8601.
</ParamField>

<ParamField query="agencyId" type="string">
  Reserved for the multi-workspace agency rollup that ships later. Today the
  endpoint returns **501 Not Implemented** if this parameter is supplied;
  call without it to get your own workspace's numbers.
</ParamField>

All filters are intersected. Omit a filter to remove that constraint.

***

## Response

```json theme={null}
{
  "success": true,
  "filter": {
    "campaignId": "60d5ec49e3a8a0001f9b1234",
    "dateFrom": "2026-06-01T00:00:00.000Z"
  },
  "metrics": {
    "reached": 1245,
    "sent": 3870,
    "fallback": 92,
    "failed": 14,
    "replied": 418,
    "uniqueReplied": 312,
    "repliedPct": 0.2506,
    "positiveReplied": 71,
    "positivePct": 0.2275
  },
  "explainer": {
    "reached": "Unique contacts that received at least one outbound message (status sent or delivered).",
    "sent": "Total outbound messages with status sent or delivered. One contact may count more than once.",
    "fallback": "Outbound messages that fell back to SMS or could not be sent via iMessage.",
    "failed": "Outbound messages that hit a hard send failure (technical error, blocked recipient, etc.).",
    "replied": "Total inbound messages from contacts in the window.",
    "uniqueReplied": "Distinct contacts that replied at least once.",
    "repliedPct": "uniqueReplied / reached. Reply rate by unique contact.",
    "positiveReplied": "Distinct contacts whose conversation carries a positive tag (interested, to respond, to-answer).",
    "positivePct": "positiveReplied / uniqueReplied. Share of replies that are warm."
  }
}
```

### Metric definitions

<ResponseField name="reached" type="number">
  Distinct contacts (phone or email) that received at least one outbound
  message with status `sent` or `delivered` inside the filter window.
</ResponseField>

<ResponseField name="sent" type="number">
  Total outbound messages with status `sent` or `delivered`. Not de-duplicated
  by contact — a 7-step campaign over 1,000 contacts can show `sent ≈ 7,000`.
</ResponseField>

<ResponseField name="fallback" type="number">
  Outbound messages with status `fallback` **or** with `fallbackSmsDispatched`
  set. Both signal iMessage didn't go through and SMS was attempted instead.
</ResponseField>

<ResponseField name="failed" type="number">
  Outbound messages with status `failed` — hard send failure (technical
  error, blocked recipient, transient-exhausted, etc.).
</ResponseField>

<ResponseField name="replied" type="number">
  Total inbound messages received inside the filter window. Includes
  multiple replies from the same contact.
</ResponseField>

<ResponseField name="uniqueReplied" type="number">
  Distinct contacts that replied at least once inside the window.
</ResponseField>

<ResponseField name="repliedPct" type="number">
  `uniqueReplied / reached`. A decimal between 0 and 1 (multiply by 100 for
  the display %). Returns 0 when `reached` is 0 — never divides by zero.
</ResponseField>

<ResponseField name="positiveReplied" type="number">
  Distinct contacts among `uniqueReplied` whose conversation document carries
  at least one positive tag: `interested`, `to respond`, `to-answer`, or
  `to_answer` (case-insensitive exact match). Tags are written by Tuco's
  reply classifier and by Unibox operators.
</ResponseField>

<ResponseField name="positivePct" type="number">
  `positiveReplied / uniqueReplied`. Share of repliers tagged warm. Returns
  0 when `uniqueReplied` is 0.
</ResponseField>

***

## Error responses

| Status | Code                   | When                                                           |
| ------ | ---------------------- | -------------------------------------------------------------- |
| `401`  | `Unauthorized`         | Missing or invalid API key / Clerk session                     |
| `501`  | `AGENCY_NOT_AVAILABLE` | `agencyId` filter supplied — feature not yet shipped           |
| `500`  | —                      | Aggregation failed; check Loki for the `analytics.error` event |

***

## Example: pull last-30-day numbers for one campaign

```bash theme={null}
curl -s -H "Authorization: Bearer tuco_..." \
  "https://app.tuco.ai/api/analytics?campaignId=60d5ec49e3a8a0001f9b1234&dateFrom=$(date -u -v-30d +%Y-%m-%dT00:00:00Z)" \
  | jq '.metrics'
```

## Example: via MCP

Configure the [Tuco MCP server](/integrations/mcp-server) and ask any
MCP-compatible AI assistant:

> "Show analytics for campaign `60d5ec49e3a8a0001f9b1234` over the last 30
> days."

The assistant calls the `analytics` MCP tool, which hits this endpoint with
your API key and returns the same metric block.

## Observability

Every successful call emits a structured `analytics.served` event to Loki:

```json theme={null}
{
  "event": "analytics.served",
  "workspaceId": "org_…",
  "authMethod": "api_key",
  "filter": { "campaignId": "…", "dateFrom": "…", "dateTo": "…" },
  "metrics": { /* the full block */ },
  "durationMs": 101
}
```

Failures emit `analytics.error` with the error message and workspace id.
