# Analytics Metrics

Aggregate video performance metrics across your tracked videos. Two complementary endpoints share the same five metrics — **views, likes, comments, reposts, saves** — sliced across a date window with optional grouping by platform, campaign, or blogger.

| Endpoint | Shape | When to use |
|----------|-------|-------------|
| `GET /v1/analytics/metrics/totals` | Single aggregated row (plus a `groups` array — one entry per group — when `group_by` is set) summed across the full `from`–`to` window | Dashboard KPIs, headline metrics, single-number reporting over a period |
| `GET /v1/analytics/metrics/increments` | Sparse per-day time series — `data` is an array of `period` entries; periods with no underlying activity are absent (no zero-fill) | Daily trend charts, week-over-week deltas, period-over-period comparisons |

Both endpoints share the same filter parameters, the same `group_by` options, the same paid-tier requirement, and the same five-metric contract.

:::warning Paid Plan Required
These endpoints require a **Pro** or **Business** subscription. Free-tier callers receive `402 PAID_PLAN_REQUIRED`. Authentication uses a Bearer API key — see [Authentication](../authentication.md).
:::

---

## Get Aggregated Totals

```
GET /v1/analytics/metrics/totals
```

Returns aggregated metrics over a date range. By default you receive a single object with the totals for the whole range; pass `group_by` to slice the totals by platform, campaign, or blogger.

### Request Parameters

| Param | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `from` | string | ✅ | — | Start of the date range, inclusive. ISO date `YYYY-MM-DD`. `from` must be on or before `to`. |
| `to` | string | ✅ | — | End of the date range, inclusive. ISO date `YYYY-MM-DD`. Range cannot exceed 365 days. |
| `group_by` | string | ✅ | — | How to slice the totals. One of: `none`, `platform`, `campaign_tag`, `blogger_username`, `platform_blogger`. |
| `platform` | string | ❌ | — | Filter to a single platform. One of: `tiktok`, `instagram`, `youtube`, `telegram`, `dzen`, `ok`, `vk`, `threads`, `pinterest`. Pass `all` (or omit) for no filter. |
| `campaign_tag` | string \| string[] | ❌ | — | One or more campaign tags. Pass a comma-separated list for multiple. |
| `blogger_username` | string \| string[] | ❌ | — | One or more channel usernames. Pass a comma-separated list for multiple. |
| `tags` | string \| string[] | ❌ | — | One or more tag UUIDs. Pass a comma-separated list for multiple. |
| `hashtags` | string \| string[] | ❌ | — | One or more hashtag names. |
| `content_type` | string \| string[] | ❌ | — | One or more content types. Supported values: `video`, `slideshow`. |
| `status` | string | ❌ | — | Campaign status filter. One of: `to do`, `running`, `ended`. Pass `all` (or omit) for no filter. |
| `video_status` | string | ❌ | — | Tracking status filter: `tracking`, `paused`, or `deleted`. |
| `published_at.from` | string | ❌ | — | Lower bound on video publish date. ISO date `YYYY-MM-DD`. |
| `published_at.to` | string | ❌ | — | Upper bound on video publish date. ISO date `YYYY-MM-DD`. |
| `tracked_at.from` | string | ❌ | — | Lower bound on when the video was added to tracking. ISO date `YYYY-MM-DD`. |
| `tracked_at.to` | string | ❌ | — | Upper bound on when the video was added to tracking. ISO date `YYYY-MM-DD`. |

The `published_at` and `tracked_at` filters are nested objects in the URL: pass them as two separate query params (`?published_at.from=...&published_at.to=...`). For each pair, the lower bound must be on or before the upper bound.

### Example

```bash
curl -X GET "https://api.influtics.com/v1/analytics/metrics/totals?from=2026-08-01&to=2026-09-08&group_by=platform" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Response

```json
{
  "success": true,
  "data": {
    "group_by_dimension": "platform",
    "from": "2026-08-01",
    "to": "2026-09-08",
    "total": {
      "views": 1234567,
      "likes": 89012,
      "comments": 1234,
      "reposts": 567,
      "saves": 89
    },
    "groups": [
      {
        "group_key": "tiktok",
        "group_label": "TikTok",
        "views": 800000,
        "likes": 50000,
        "comments": 800,
        "reposts": 400,
        "saves": 60
      },
      {
        "group_key": "youtube",
        "group_label": "YouTube",
        "views": 434567,
        "likes": 39012,
        "comments": 434,
        "reposts": 167,
        "saves": 29
      }
    ]
  },
  "meta": {
    "request_id": "uuid",
    "credits_used": 1
  }
}
```

When `group_by=none`, `groups` is an empty array and the only metric values you need are in `total`. When `group_by=platform_blogger`, each entry's `group_label` is rendered as `"<channel_username> - <platform>"` (for example `"lifestyle_vlog - YouTube"`).

The five metrics always appear in the same order — **views, likes, comments, reposts, saves** — in both `total` and each group entry.

---

## Get Daily Increments

```
GET /v1/analytics/metrics/increments
```

Returns a sparse per-day time series of metric increments across the date range. Periods with no underlying activity are omitted from the response (no zero-fill), and periods are sorted ascending by `period`. Same filters, same five-metric contract, same grouping options as `/totals`.

### Request Parameters

Same as [`GET /v1/analytics/metrics/totals`](#get-aggregated-totals) above. All filter parameters behave identically.

### Example

```bash
curl -X GET "https://api.influtics.com/v1/analytics/metrics/increments?from=2026-08-01&to=2026-09-08&group_by=platform" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Response

```json
{
  "success": true,
  "data": {
    "group_by_dimension": "platform",
    "from": "2026-08-01",
    "to": "2026-09-08",
    "data": [
      {
        "period": "2026-08-15",
        "date": "2026-08-15",
        "groups": {
          "tiktok": {
            "key": "tiktok",
            "label": "TikTok",
            "views": 3000,
            "likes": 180,
            "comments": 10,
            "reposts": 3,
            "saves": 1
          },
          "youtube": {
            "key": "youtube",
            "label": "YouTube",
            "views": 1123,
            "likes": 50,
            "comments": 2,
            "reposts": 1,
            "saves": 0
          }
        }
      },
      {
        "period": "2026-08-16",
        "date": "2026-08-16",
        "groups": {
          "tiktok": {
            "key": "tiktok",
            "label": "TikTok",
            "views": 2876,
            "likes": 165,
            "comments": 8,
            "reposts": 2,
            "saves": 1
          }
        }
      }
    ]
  },
  "meta": {
    "request_id": "uuid",
    "credits_used": 1
  }
}
```

:::note Totals vs. Sum-of-Increments
Summing the metric values across every entry in `data[*].groups[*]` over the window will not necessarily equal the `total` returned by [`/v1/analytics/metrics/totals`](#get-aggregated-totals) for the same filters. The two endpoints measure the same period with different semantics: `/totals` is the source of truth — it computes each video's gain as `value-at-window-end − value-at-window-start` — while `/increments` is a per-day time series of what the underlying materialized view recorded on each day. They diverge in three documented ways:

1. **No baseline credit when `published_at` ≠ first stat_date.** When a video already has views at the moment tracking starts (typical for old viral videos where `videos.published_at` is far in the past), the MV's first row's `views_increment` falls into the gap-day branch and is recorded as `0` rather than the full pre-existing count. `/totals` doesn't have this constraint — it just does `end_views − start_views`, treating `start_views` as `0` when there's no MV row on or before `from`. So `/totals` credits the entire pre-existing count while `/increments` doesn't count it at all. Window scope matters: if `from` is on or after the video's first MV row, both endpoints use that row as their baseline and agree; the divergence only appears when `from` precedes the first MV row.
2. **Dips below the start value.** `/increments` floors daily increments at `0` (no negatives) when a video's count drops and recovers inside the window; `/totals` still credits the recovery, so its gain is larger.
3. **Source-platform counter resets.** When the platform resets a counter (e.g. analytics deletion), the MV records that day as `0` rather than a negative; `/totals` reflects the full end-vs-start delta, `/increments` does not.

Use `/totals` when you need a single number that represents the period; use `/increments` for day-by-day trend plots. Don't compare them by summing.
:::

When `group_by=none`, each `period` object contains a single group named `"total"` (with `"key": "total"`, `"label": "Total"`) carrying that day's aggregate increments. When `group_by=platform` or `group_by=blogger_username`, each `period` contains one group entry per active group on that day; groups absent on a given day are omitted entirely.

The five metrics always appear in the same order — **views, likes, comments, reposts, saves** — in every `groups` entry.

---

## Error Responses

| Status | Code | When |
|--------|------|------|
| 400 | `VALIDATION_ERROR` | Missing/invalid `from` or `to` (not ISO, `from > to`, range > 365 days), unknown `group_by`, or unsupported nested-date ordering. |
| 401 | `AUTHENTICATION_FAILED` | Missing or invalid API key. |
| 402 | `PAID_PLAN_REQUIRED` | Caller's subscription is on the free tier. |
| 500 | `INTERNAL_ERROR` | Upstream RPC failure or unexpected handler error. |
