
# Analytics & Reports API

These read-only endpoints let you pull your account's activity into your own dashboards and reports: message-event counts, credit consumption, AI spend, and the same charts and insights the in-app dashboard shows. This guide covers:

- **Summary** — message-volume counters (sent, delivered, read, replied, booked, contacts created, credits).
- **Credits** — a detailed, paginated ledger of credit usage with totals and breakdowns.
- **AI cost** — a per-day rollup of AI spend.
- **Metric series** — a chart-ready time series for one or more metrics, grouped by campaign, channel, AI Agent, or number.
- **Conversation outcomes** — how conversations ended, by AI-assigned outcome tag.
- **Dashboard insights** and **Dashboard AI insights** — the full data behind the in-app dashboard, including AI-written summaries.
- **Entity activity** — a single contact's, deal's, or task's timeline.
- **Aggregated event counts** — a legacy, camelCase form of Summary kept for existing integrations.

Every endpoint on this page needs an exact scope, not both: pass at most one of `campaign_id` (legacy) or `agent_id` where the endpoint accepts it. Sending both returns `400`, and an id that is not on your account returns `404` rather than `403`, so other accounts' ids stay unguessable.

All paths below are relative to the API base URL:

```
https://api.youraiconnector.com/v1
```

Every request must be authenticated. See [Authentication](authentication.md) for the four accepted methods. The examples here use the `X-API-Key` header (and one query-parameter form for cURL).

---

## Date range

All three endpoints accept the same optional date filters:

| Parameter | Description |
|---|---|
| `from` | Start of the range, `YYYY-MM-DD`, inclusive. Defaults to 30 days ago. |
| `to` | End of the range, `YYYY-MM-DD`, inclusive. Defaults to today. |

Dates are interpreted in UTC. The range defaults to the **last 30 days** and is capped at **366 days** — a wider range returns `400`. `from` must not be after `to`.

### The `truncated` flag

The **Summary** and **Credits** endpoints cap how many records a single request scans. If your range is busy enough to hit that cap, the response includes `"truncated": true`. When you see it, the numbers are based on a partial scan — narrow your date range (or page through with a smaller window) to get complete figures.

::: note
**Note:** Cost and token figures are only included for AI calls billed to your own provider API keys. When cost figures are hidden for your account, the response sets `"costs_redacted": true` and the cost fields are returned as zero.
:::


---

## Message volume summary

Returns aggregated message-event counters for your account, both as range totals and as a per-day series. Every day in the range appears in `by_date` — quiet days are zero-filled. Optionally filter to a single campaign with `campaign_id`.

`GET /analytics/summary`

| Parameter | Required | Description |
|---|---|---|
| `from` | No | Range start, `YYYY-MM-DD`. |
| `to` | No | Range end, `YYYY-MM-DD`. |
| `campaign_id` | No | Only count events belonging to this campaign. |

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/analytics/summary?from=2026-05-01&to=2026-05-31&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({ from: "2026-05-01", to: "2026-05-31" });
const res = await fetch(`https://api.youraiconnector.com/v1/analytics/summary?${params}`, {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/analytics/summary",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"from": "2026-05-01", "to": "2026-05-31"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "from": "2026-05-01",
  "to": "2026-05-31",
  "totals": {
    "total": 1240,
    "sent": 800,
    "delivered": 760,
    "read": 540,
    "replied": 210,
    "booked": 35,
    "contact_created": 120,
    "credits_spent": 412.5,
    "credits_recharged": 500
  },
  "by_date": [
    {
      "date": "2026-05-01",
      "total": 40,
      "sent": 25,
      "delivered": 24,
      "read": 18,
      "replied": 7,
      "booked": 1,
      "contact_created": 4,
      "credits_spent": 13.5,
      "credits_recharged": 0
    }
  ],
  "truncated": false
}
```

Each entry in `by_date` has the same counter fields as `totals`, plus a `date`.

If you pass a `campaign_id` that does not belong to your account, the response is `404` with `{ "success": false, "error": "Campaign not found" }`.

---

## Credit usage

Returns credit usage over the range: a paginated list of individual records, plus range totals and breakdowns by reason and by campaign.

`GET /analytics/credits`

| Parameter | Required | Description |
|---|---|---|
| `from` | No | Range start, `YYYY-MM-DD`. |
| `to` | No | Range end, `YYYY-MM-DD`. |
| `campaign_id` | No | Only include usage attributed to this campaign. |
| `limit` | No | Page size for `records`, 1–100. Defaults to 50. |
| `cursor` | No | Pass the previous page's `next_cursor` to fetch the next page. |

> **Adjustments vs. consumption:** Balance changes such as bonuses, plan renewals, and corrections are **excluded** from `totals` and the breakdowns — they are not real consumption. They still appear in the `records` list, flagged with `"is_adjustment": true`.

**Totals and breakdowns appear only on the first page** (when no `cursor` is supplied). On later pages, `totals`, `by_reason`, `by_reason_cost`, and `by_campaign` are returned as `null` — only the `records` array continues. This avoids re-scanning the whole range for every page.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/analytics/credits?from=2026-05-01&to=2026-05-31&limit=50&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({
  from: "2026-05-01",
  to: "2026-05-31",
  limit: "50",
});
const res = await fetch(`https://api.youraiconnector.com/v1/analytics/credits?${params}`, {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();

// To page: pass data.next_cursor as ?cursor on the next request, until it is null.
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/analytics/credits",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"from": "2026-05-01", "to": "2026-05-31", "limit": 50},
)
data = res.json()

# To page: pass data["next_cursor"] as cursor on the next request, until it is None.
```

**Response** (first page)

```json
{
  "success": true,
  "from": "2026-05-01",
  "to": "2026-05-31",
  "totals": {
    "credits_used": 412.5,
    "cost_usd": 1.284512,
    "records": 318
  },
  "by_reason": {
    "AI Message": 380.0,
    "Campaign Message": 32.5
  },
  "by_reason_cost": {
    "AI Message": 1.284512,
    "Campaign Message": 0
  },
  "by_campaign": {
    "Spring Promo": 250.0,
    "Reactivation": 162.5
  },
  "records": [
    {
      "id": "rec_abc123",
      "amount": 1,
      "timestamp": "2026-05-31T14:02:11.000Z",
      "reason": "AI Message",
      "is_adjustment": false,
      "campaign_id": "campaign123",
      "campaign_name": "Spring Promo",
      "contact_id": "contact456",
      "contact_name": "Jane Smith",
      "credit_type": "ai",
      "custom_keys_used": false,
      "description": null,
      "cost_usd": 0,
      "input_tokens": 0,
      "output_tokens": 0,
      "cache_read_tokens": 0,
      "cache_creation_tokens": 0,
      "ai_model": null,
      "request_id": null,
      "is_test": false
    }
  ],
  "next_cursor": "rec_abc123",
  "costs_redacted": false,
  "truncated": false
}
```

**Field notes:**

- `amount` — credits charged for the record. Zero for records billed to your own provider API key.
- `is_adjustment` — `true` for balance changes (excluded from totals/breakdowns).
- `cost_usd`, `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_creation_tokens`, `ai_model`, `request_id` — populated only on records billed to your own provider API key; zero or `null` otherwise.
- `is_test` — `true` for playground/test runs, which are never billed.
- `next_cursor` — the cursor for the next page, or `null` when there are no more records.

---

## AI cost rollup

Returns the per-day AI spend rollup for your account. This reads pre-aggregated daily totals, so it is fast even over long ranges. Every day in the range appears in `days` — quiet days are zero-filled.

`GET /analytics/ai-cost`

| Parameter | Required | Description |
|---|---|---|
| `from` | No | Range start, `YYYY-MM-DD`. |
| `to` | No | Range end, `YYYY-MM-DD`. |

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/analytics/ai-cost?from=2026-05-01&to=2026-05-31&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({ from: "2026-05-01", to: "2026-05-31" });
const res = await fetch(`https://api.youraiconnector.com/v1/analytics/ai-cost?${params}`, {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/analytics/ai-cost",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"from": "2026-05-01", "to": "2026-05-31"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "from": "2026-05-01",
  "to": "2026-05-31",
  "totals": {
    "total_usd": 12.4821,
    "byok_usd": 12.4821,
    "platform_usd": 0,
    "calls": 4210
  },
  "days": [
    {
      "date": "2026-05-01",
      "total_usd": 0.4012,
      "byok_usd": 0.4012,
      "platform_usd": 0,
      "input_usd": 0.18,
      "output_usd": 0.19,
      "cache_creation_usd": 0.02,
      "cache_read_usd": 0.0112,
      "calls": 140,
      "by_provider": { "anthropic": 0.4012 }
    }
  ],
  "costs_redacted": false
}
```

**Field notes:**

- `byok_usd` — spend billed to your own provider API keys.
- `platform_usd` — the portion of spend that ran on the platform rather than your own key.
- `input_usd`, `output_usd`, `cache_creation_usd`, `cache_read_usd` — the cost components that make up `total_usd`.
- `by_provider` — USD spend keyed by AI provider name.
- USD figures are only returned to accounts that use their own provider key. For credit-paying accounts every USD field is zero and `costs_redacted` is `true` (call counts stay visible).

---

## Metric series

Returns one or more metric time series in one call, optionally grouped by up to two dimensions — the endpoint to bind a chart to. A single request can answer "sent and replied per day, per channel, for this campaign" without one call per campaign.

`GET /analytics/series`

Every response carries a `labels` array (the time axis, zero-filled across the whole range) and one entry in `series` per group, each holding one array per requested metric aligned to `labels`. Series past `limit` are not dropped — they collapse into `other_bucket`, computed as the range total minus the returned series, so a rendered chart always adds up to your real numbers; `truncated` is `true` whenever that happens.

Where the numbers come from: `sent`, `delivered`, `read`, and `replied` come from message records, which carry the channel and sending number. `booked`, `contact_created`, and `credits_spent` come from the event stream, which carries no sending number, so those metrics land in the `null`-number bucket when you group by `number`.

| Parameter | Required | Description |
|---|---|---|
| `from` | No | Range start, `YYYY-MM-DD`. Defaults to 30 days ago. |
| `to` | No | Range end, `YYYY-MM-DD`. Defaults to today. |
| `metrics` | No | Comma-separated list from `sent`, `ai_sent`, `human_sent`, `delivered`, `read`, `replied`, `booked`, `contact_created`, `credits_spent`. Defaults to `sent,replied`. An unknown metric returns `400`. |
| `group_by` | No | Comma-separated list of up to two dimensions from `date`, `campaign`, `channel`, `agent`, `number`. `date` is accepted but has no effect — every response already carries the time axis. Omit for a single account-wide series. |
| `granularity` | No | `day` (default), `week`, or `month`. Week buckets start on Monday, month buckets on the 1st. |
| `limit` | No | How many series to return before the rest collapse into `other_bucket`, 1–50. Defaults to 12. |
| `campaign_id` | No | Only count activity belonging to this campaign. Legacy; prefer `agent_id`. |
| `agent_id` | No | Only count activity belonging to this AI Agent. |
| `channel` | No | Only count activity on this channel, for example `whatsapp`. |

This endpoint's date range is capped at **92 days** (tighter than the 366-day cap used elsewhere on this page).

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/analytics/series?from=2026-05-01&to=2026-05-31&metrics=sent,replied,booked&group_by=campaign,channel&limit=10&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({
  from: "2026-05-01",
  to: "2026-05-31",
  metrics: "sent,replied,booked",
  group_by: "campaign,channel",
  limit: "10",
});
const res = await fetch(`https://api.youraiconnector.com/v1/analytics/series?${params}`, {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/analytics/series",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={
        "from": "2026-05-01",
        "to": "2026-05-31",
        "metrics": "sent,replied,booked",
        "group_by": "campaign,channel",
        "limit": 10,
    },
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "from": "2026-05-01",
  "to": "2026-05-31",
  "granularity": "day",
  "labels": ["2026-05-01", "2026-05-02"],
  "group_by": ["campaign", "channel"],
  "metrics": ["sent", "replied", "booked"],
  "series": [
    {
      "key": {
        "campaign_id": "campaign123",
        "campaign_name": "Spring Promo",
        "channel": "whatsapp"
      },
      "total": 812,
      "metrics": {
        "sent": [40, 35],
        "replied": [12, 9],
        "booked": [2, 1]
      }
    }
  ],
  "other_bucket": {
    "series_count": 6,
    "total": 340,
    "metrics": {
      "sent": [18, 20],
      "replied": [5, 6],
      "booked": [0, 1]
    }
  },
  "truncated": true
}
```

**Field notes:**

- `key` — the identity of one series. Only the keys for the requested `group_by` dimensions are present; a dimension whose value is unknown for a row (a message with no campaign, an event with no channel) comes back as `null` rather than being dropped, so series still add up to the totals.
- `other_bucket` — `null` when nothing was collapsed.
- This endpoint returns `503` with `"error_code": "analytics_unavailable"` when the reporting database cannot answer for your account, rather than a `200` full of zeros — a zeroed chart would read as fact.

---

## Conversation outcomes

Returns how conversations ended over a date range: a per-day count for each outcome tag the AI assigned, plus range totals for replies, bookings, handovers to a human, and conversations the AI never classified.

`GET /analytics/outcomes`

Pass `group_by=tag` to collapse the time axis and get range totals per tag only — in that mode `labels` is empty and each tag's `counts` array is empty, while `total` is still populated.

| Parameter | Required | Description |
|---|---|---|
| `from` | No | Range start, `YYYY-MM-DD`. Defaults to 30 days ago. |
| `to` | No | Range end, `YYYY-MM-DD`. Defaults to today. |
| `campaign_id` | No | Only count conversations with contacts currently on this campaign. Legacy; prefer `agent_id`. |
| `agent_id` | No | Only count outcomes belonging to this AI Agent. |
| `group_by` | No | `date` (default) keeps the per-day counts; `tag` collapses to range totals. |

This endpoint's date range is capped at **92 days**.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/analytics/outcomes?from=2026-05-01&to=2026-05-31&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({ from: "2026-05-01", to: "2026-05-31" });
const res = await fetch(`https://api.youraiconnector.com/v1/analytics/outcomes?${params}`, {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/analytics/outcomes",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"from": "2026-05-01", "to": "2026-05-31"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "from": "2026-05-01",
  "to": "2026-05-31",
  "group_by": "date",
  "labels": ["2026-05-01", "2026-05-02"],
  "by_tag": [
    { "tag": "interested", "total": 84, "counts": [3, 5] },
    { "tag": "not_interested", "total": 40, "counts": [1, 2] },
    { "tag": null, "total": 12, "counts": [0, 1] }
  ],
  "totals": {
    "sessions": 260,
    "replied": 210,
    "booked": 35,
    "human_alerted": 18,
    "unresolved": 12
  }
}
```

**Field notes:**

- `by_tag[].tag` — `null` for conversations the AI never assigned an outcome tag to.
- `totals.human_alerted` — conversations handed over to a human; this is written on every handover and was not previously surfaced by any endpoint.
- Same `503`/`analytics_unavailable` posture as Metric series when the reporting database cannot answer.

---

## Dashboard insights

Returns the full dashboard payload for a date range in one call: a reply-rate heatmap by weekday and hour, the campaign leaderboard, per-channel volume, exact per-connection totals, per-day metric breakdowns (account-wide, per channel, and per number), where contacts are from, inbox response time, and a recent activity feed. This is the richest reporting payload on the API — it powers the in-app dashboard directly.

`GET /analytics/dashboard-insights`

| Parameter | Required | Description |
|---|---|---|
| `startDate` | Yes | Range start, `YYYY-MM-DD`. |
| `endDate` | Yes | Range end, `YYYY-MM-DD`. |
| `campaignId` | No | Only include activity belonging to this campaign (`campaign_id` also accepted). Legacy; prefer `agent_id`. |
| `agent_id` | No | Only include activity belonging to this AI Agent (`agentId` also accepted). Under an agent scope, the campaign leaderboard is built from that agent's activity only. |

This endpoint uses `startDate`/`endDate` (not `from`/`to`) because it shares its implementation with the in-app dashboard. The range is capped at 92 days and is **clamped, not rejected**, when it is wider.

> **Null means unavailable, not zero.** Several blocks (`numberStats`, `channelDailySeries`, `metricDailyBreakdown`, `contactsByCountry`) are computed from the reporting database and come back `null` when it cannot answer for your account. Do not render a `null` block as an empty chart.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/analytics/dashboard-insights?startDate=2026-05-01&endDate=2026-05-31&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({ startDate: "2026-05-01", endDate: "2026-05-31" });
const res = await fetch(`https://api.youraiconnector.com/v1/analytics/dashboard-insights?${params}`, {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/analytics/dashboard-insights",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"startDate": "2026-05-01", "endDate": "2026-05-31"},
)
data = res.json()
```

**Response** (abridged — this payload is large; see the [API Reference](reference.md) for the full schema)

```json
{
  "success": true,
  "data": {
    "heatmap": {
      "buckets": [
        { "weekday": 1, "hour": 9, "sent": 12, "replied": 5, "replyRate": 0.42 }
      ]
    },
    "topCampaigns": [
      { "campaignId": "campaign123", "name": "Spring Promo", "sent": 420, "replied": 180, "booked": 22, "replyRate": 0.43, "creditsSpent": 210.5 }
    ],
    "channelVolume": [
      { "channel": "whatsapp", "sent": 800, "received": 540, "lastMessageAt": "2026-05-31T14:02:11.000Z" }
    ],
    "inboxSla": { "medianFirstResponseMs": 92000, "sampleSize": 140 },
    "activityFeed": [
      { "id": "evt_1", "kind": "booked", "at": "2026-05-31T14:02:11.000Z", "contactId": "contact456", "contactName": "Jane Smith", "campaignId": "campaign123", "campaignName": "Spring Promo", "label": "Jane Smith booked an appointment" }
    ],
    "numberStats": null,
    "channelDailySeries": null,
    "metricDailyBreakdown": null,
    "contactsByCountry": null,
    "ai_human_split": null
  }
}
```

**Field notes:**

- `heatmap.buckets[].weekday` — `0` is Sunday through `6` is Saturday.
- `numberStats`, `channelDailySeries`, `metricDailyBreakdown`, `contactsByCountry`, `ai_human_split` — each independently `null` when the reporting database is unavailable for your account; every other block still returns.

---

## Dashboard AI insights

Returns three short, AI-written insights about the account's messaging over a date range: one win, one thing to watch, and one tip — sentences you can paste straight into a report rather than numbers you still have to interpret. Generated from the account's own message metrics only.

`GET /analytics/dashboard-ai-insights`

| Parameter | Required | Description |
|---|---|---|
| `startDate` | Yes | Range start, `YYYY-MM-DD`. |
| `endDate` | Yes | Range end, `YYYY-MM-DD`. |

This endpoint is account-wide — it takes no campaign or agent scope.

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/analytics/dashboard-ai-insights?startDate=2026-05-01&endDate=2026-05-31&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({ startDate: "2026-05-01", endDate: "2026-05-31" });
const res = await fetch(`https://api.youraiconnector.com/v1/analytics/dashboard-ai-insights?${params}`, {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/analytics/dashboard-ai-insights",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"startDate": "2026-05-01", "endDate": "2026-05-31"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "data": {
    "insights": [
      { "tone": "win", "title": "Reply rate is up", "detail": "Your reply rate climbed to 43% this period, up from 36% the period before." },
      { "tone": "watch", "title": "Bookings slowed midweek", "detail": "Wednesday bookings dropped to a third of Monday's, worth a look at your Wednesday follow-up timing." },
      { "tone": "tip", "title": "Re-send to non-repliers", "detail": "212 contacts received a message but never replied — a short follow-up template often recovers 10-15% of them." }
    ]
  }
}
```

Missing `startDate` or `endDate` returns `400`.

---

## Entity activity timeline

Returns a single contact's, deal's, or task's activity as one timeline, newest first: what happened and when, across messages, appointments, notes, and status changes. Use it to answer "what has happened with this person" without stitching several list endpoints together.

`GET /analytics/entity-activity`

| Parameter | Required | Description |
|---|---|---|
| `entityType` | Yes | `contact`, `deal`, or `task`. |
| `entityId` | Yes | ID of the record whose timeline to return. |

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/analytics/entity-activity?entityType=contact&entityId=contact456&apiKey=YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({ entityType: "contact", entityId: "contact456" });
const res = await fetch(`https://api.youraiconnector.com/v1/analytics/entity-activity?${params}`, {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
```

**Python**

```python
import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/analytics/entity-activity",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"entityType": "contact", "entityId": "contact456"},
)
data = res.json()
```

**Response**

```json
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "evt_9",
        "kind": "appointment_booked",
        "at": "2026-05-31T14:02:11.000Z",
        "label": "Booked an appointment for June 3",
        "detail": "Consultation call, 30 minutes"
      },
      {
        "id": "evt_8",
        "kind": "message_replied",
        "at": "2026-05-31T13:58:02.000Z",
        "label": "Replied: \"Yes, that time works\""
      }
    ]
  }
}
```

A missing or invalid `entityType`/`entityId` returns `400`. An entity that does not exist on your account returns `404`, so other accounts' ids stay unguessable.

---

## Aggregated event counts (legacy)

Returns the same aggregated event counts as [Message volume summary](#message-volume-summary), but in the camelCase shape (`contactCreated` rather than `contact_created`, `byDate` rather than `by_date`) some older integrations were built against. Prefer `/analytics/summary` for new integrations — this endpoint exists only so the in-app dashboard and the API share one implementation.

`GET /analytics/aggregate`

| Parameter | Required | Description |
|---|---|---|
| `startDate` | No | Range start, ISO date or date-time. Defaults to the same window `/analytics/summary` uses. |
| `endDate` | No | Range end, ISO date or date-time. |
| `campaignId` | No | Only count events belonging to this campaign (`campaign_id` also accepted). Legacy; prefer `agent_id`. |
| `agent_id` | No | Only count events belonging to this AI Agent (`agentId` also accepted). |

**cURL**

```bash
curl "https://api.youraiconnector.com/v1/analytics/aggregate?startDate=2026-05-01&endDate=2026-05-31&apiKey=YOUR_API_KEY"
```

**Response**

```json
{
  "success": true,
  "data": {
    "from": "2026-05-01",
    "to": "2026-05-31",
    "total": 1240,
    "byAnalyticType": {
      "total": 1240,
      "sent": 800,
      "delivered": 760,
      "read": 540,
      "replied": 210,
      "booked": 35,
      "contactCreated": 120,
      "creditsSpent": 412.5,
      "creditsRecharged": 500
    },
    "byDate": [
      { "date": "2026-05-01", "byAnalyticType": { "total": 40, "sent": 25, "delivered": 24, "read": 18, "replied": 7, "booked": 1, "contactCreated": 4, "creditsSpent": 13.5, "creditsRecharged": 0 } }
    ]
  }
}
```

---

## Agency sub-account rollup


---

## Analytics API errors

Analytics endpoints return the standard error envelope:

```json
{
  "success": false,
  "error": "Date range too large. Maximum is 366 days."
}
```

On an analytics endpoint, an invalid date format or an out-of-range window returns `400`, and an unknown `campaign_id` or `agent_id` returns `404`. Sending both `campaign_id` and `agent_id` on an endpoint that accepts either is also a `400` — pass at most one. The PG-only reporting endpoints (Metric series, Conversation outcomes, Agency rollup) return `503` with `"error_code": "analytics_unavailable"` rather than a `200` full of zeros when the reporting database cannot answer for your account — retry shortly. The shared codes every endpoint can return — `401`, `403` (your plan does not include API access, or, on the agency rollup, your account is not Agency/Dev), `429` (rate limit) and `500` — are listed with retry guidance in [Errors & Pagination](errors-and-pagination.md).

---

## Next steps

- [Authentication](authentication.md) — the four ways to authenticate a request.
- [Errors & Rate Limits](errors-and-pagination.md) — status codes and the 300 req/min limit.
- [Campaigns API](campaigns.md) — the campaigns these figures can be filtered by.
