Aggregate analytics
Project-level aggregate endpoints for leaderboards, heatmaps, histograms, scatter charts, and membership overlap.
Overview#
The aggregate analytics endpoints return pre-computed project-level summaries that power the Analytics section (Video, Creator, and Campaign sub-tabs). They differ from the unified /api/v2/analytics endpoint in that they aggregate across all entities in a project rather than for a single entity.
All endpoints:
- Accept both Bearer API key and session cookie authentication
- Return the standard
{ data, error, meta }envelope - Require the caller to have access to the specified
project_id - Are not cached (no Vercel Runtime Cache wrapper): responses are fresh on every request
Base path: /api/v2/analytics/aggregate
Totals#
GET /aggregate/summary#
Headline totals for a project, one campaign, or every campaign at once, over a date window. The dashboard, every Insights summary strip and Program Health read their headline numbers from this query, so the numbers match the app.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
project_id | UUID | required | Project to total |
from | YYYY-MM-DD | 29 days before to | Period start (inclusive, org time zone) |
to | YYYY-MM-DD | today (org time zone) | Period end (inclusive) |
campaign_id | UUID | None | Only this campaign (sub-campaigns and member creators included) |
campaigns | all | None | The distinct union of every campaign's videos, each video counted once (ignored with campaign_id) |
platform | tiktok | instagram | youtube | None | Only one platform |
Response data
{
"tracked_posts": 2246,
"views": 10171040,
"likes": 1711759,
"comments": 6972,
"shares": 26461,
"saves": 33878,
"engagement_rate": 17.49,
"creators_posting": 85,
"accounts_posting": 85,
"roster_creators": 106,
"roster_accounts": 110,
"platforms": 2,
"total_followers": 208181,
"follower_growth": 3958,
"pending_review_posts": 61,
"posts_without_history": 177,
"stats": { "posts": 2246, "within_48h": 1690, "never_scraped": 0, "oldest_at": "2026-09-11T10:30:30Z", "newest_at": "2026-10-09T19:31:00Z" }
}views is the current lifetime views of the tracked posts posted in the window (not views gained during it). engagement_rate is a percentage, null without views. follower_growth is measured per account (an account added mid-window counts from its first snapshot) and summed. pending_review_posts are posts in the window awaiting review; they are not in any other number. stats says how fresh the video stats behind the numbers are. meta echoes from, to and timezone.
Video endpoints#
GET /aggregate/video-leaderboard#
Returns a ranked list of tracked videos in the project for the given date range.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
project_id | UUID | required | Project to query |
from | YYYY-MM-DD | None | Period start (inclusive) |
to | YYYY-MM-DD | None | Period end (inclusive) |
order | enum | view_count | Sort column: view_count, like_count, comment_count, share_count, save_count, posted_at |
platform | tiktok | instagram | youtube | None | Restrict to videos on a single platform |
campaign_id | UUID | None | Restrict to videos the campaign resolves to (must belong to project_id) |
limit | int | 100 | Max rows (1 to 200) |
offset | int | 0 | Pagination offset |
Response data array: each item:
{
"video_id": "uuid",
"title": "string | null",
"thumbnail_url": "string | null",
"url": "string | null",
"view_count": 12345,
"like_count": 678,
"comment_count": 45,
"share_count": 12,
"save_count": 89,
"posted_at": "2026-04-01T12:00:00Z",
"creator_id": "uuid",
"handle": "string",
"display_name": "string | null",
"avatar_url": "string | null",
"total_count": 87
}total_count is the total matching rows (before limit/offset). handle is the creator's primary account handle. Pass platform to restrict the leaderboard to videos on one platform.
GET /aggregate/posting-heatmap#
Returns a DOW × hour grid of post counts and average view counts, bucketed in the org's timezone.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
project_id | UUID | required | |
from | YYYY-MM-DD | required | |
to | YYYY-MM-DD | required | |
tz | IANA string | org timezone | Override timezone |
platform | tiktok | instagram | youtube | None | Restrict to posts on a single platform |
Response data array: each item:
{ "dow": 1, "hour": 14, "post_count": 5, "avg_views": 12430.50 }dow: 0=Sunday … 6=Saturday. Only cells with at least one post are returned; missing cells = 0.
GET /aggregate/view-distribution#
Returns a histogram of video view counts with percentile markers.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
project_id | UUID | required | |
from | YYYY-MM-DD | required | |
to | YYYY-MM-DD | required | |
bin_count | int | 20 | Number of histogram bins (5 to 50) |
platform | tiktok | instagram | youtube | None | Restrict to videos on a single platform |
Response data object:
{
"total": 87,
"min": 100,
"max": 5200000,
"p50": 12000,
"p75": 48000,
"p90": 210000,
"p99": 1800000,
"bins": [
{ "bin_index": 0, "bin_lower": 100, "bin_upper": 260250, "video_count": 61 },
...
]
}GET /aggregate/outliers#
Returns videos that outperform their creator's median view count by at least min_x_multiple, sorted by x_multiple descending.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
project_id | UUID | required | |
from | YYYY-MM-DD | None | Optional period filter |
to | YYYY-MM-DD | None | Optional period filter |
min_x_multiple | number | 1.5 | Minimum outperformance ratio |
platform | tiktok | instagram | youtube | None | Restrict to videos on a single platform |
limit | int | 50 | Max rows (1 to 100) |
Response data array: each item includes video_id, creator_id, view_count, median_views, x_multiple, posted_at, plus enriched title, thumbnail_url, url, handle, display_name, avatar_url.
Creator endpoints#
GET /aggregate/creator-leaderboard#
Returns creators in the project ranked by views in the period, with follower delta and engagement metrics.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
project_id | UUID | required | |
from | YYYY-MM-DD | None | |
to | YYYY-MM-DD | None | |
platform | tiktok | instagram | youtube | None | Restrict to creators/videos on a single platform |
limit | int | 100 | Max rows (1 to 200) |
offset | int | 0 |
Response data array: each item:
{
"creator_id": "uuid",
"handle": "string",
"display_name": "string | null",
"avatar_url": "string | null",
"current_followers": 45000,
"follower_delta": 1200,
"total_views": 980000,
"posts_in_period": 14,
"posts_per_week": 1.0,
"engagement_rate": 3.45,
"growth_rate": 2.74,
"total_count": 7
}follower_delta = follower count at end of period minus start of period (from creator_stats_history). Zero when no snapshots in range.
engagement_rate = (likes + comments + shares) ÷ views × 100 for videos posted in the period.
GET /aggregate/posting-consistency#
Returns per-creator, per-day post counts for building a punch-card matrix.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
project_id | UUID | required | |
from | YYYY-MM-DD | required | |
to | YYYY-MM-DD | required | |
tz | IANA string | org timezone | |
platform | tiktok | instagram | youtube | None | Restrict to posts on a single platform |
Response data array: sparse (only days with posts):
{ "creator_id": "uuid", "day": "2026-04-05", "post_count": 2 }Every (creator, day) pair in the range is returned, ordered by creator then day. (Before 2026-10-08 the response was silently truncated at 1,000 rows.)
GET /aggregate/over-posting-risks#
Flags creator/day combinations where a creator posted enough times in a single day that some videos were held back from tracking (suppressed) to control scrape cost.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
project_id | UUID | required | |
from | YYYY-MM-DD | required | |
to | YYYY-MM-DD | required | |
tz | IANA string | org timezone | Override timezone |
platform | tiktok | instagram | youtube | None | Restrict to posts on a single platform |
Response data array: each item:
{
"creator_handle": "string",
"creator_id": "uuid",
"post_date": "2026-04-05",
"total_posts": 9,
"suppressed_count": 3,
"suppressed_video_ids": ["uuid", "uuid", "uuid"],
"creator_last_scraped_at": "2026-04-06T02:00:00Z"
}creator_last_scraped_at is the most recent scrape across all of the creator's platform accounts (null if never scraped).
GET /aggregate/creator-scatter#
Returns per-creator (follower change, engagement rate) pairs for scatter chart rendering.
Query parameters: project_id, optional from/to, optional platform (tiktok | instagram | youtube) to restrict to a single platform.
Response data array:
{
"creator_id": "uuid",
"handle": "string",
"follower_delta": 1200,
"engagement_rate": 3.45,
"posts_count": 14
}Only creators with at least one tracked post in the period are included.
GET /aggregate/goal-compliance#
Returns a project-wide roll-up of creator goal statuses across every creator with active goals.
Query parameters: project_id.
Response data object:
{
"on_track": 3,
"at_risk": 1,
"missed": 1,
"total_goals": 5,
"creators_with_goals": 5,
"with_advisories": 4
}on_track includes completed statuses. at_risk includes behind_pace.
with_advisories is the count of goals where the advisory.has_advisory flag is true, i.e., goals whose displayed progress may not yet be final (period in progress, period recently closed, stale scrape, or goal not yet started). See the Goal Compliance endpoint for per-goal advisory detail.
Campaign endpoints#
GET /aggregate/campaign-leaderboard#
Returns campaigns in the project sorted by views, with prior-period delta comparison.
Query parameters: from, to (optional; default the last 30 days including today, org time zone); project_id and/or campaign_id. Pass project_id alone for every campaign in the project, or campaign_id to return just that one campaign's row, in which case project_id is optional: it's resolved from the campaign (a campaign outside your org returns 404). Add platform (tiktok | instagram | youtube) to count only that platform's posts, in both the current and the prior period. The response meta echoes the resolved project_id and platform.
Response data array:
{
"campaign_id": "uuid",
"name": "string",
"total_views": 1200000,
"total_likes": 45000,
"video_count": 32,
"creator_count": 5,
"preview_thumbnail_url": "string | null",
"prior_views": 980000,
"views_delta": 220000,
"views_delta_pct": 22
}Prior period is automatically computed as the same duration immediately preceding from. Each row counts its campaign's videos once; a video in two campaigns appears in both rows, so don't add rows up for a total: use /aggregate/summary with campaigns=all. creator_count is the number of creators with a tracked post in the window.
GET /aggregate/campaign-timeseries#
Returns the trend series behind the Campaign Insights charts: impressions, posts, distinct creators posting, and first-time creators per time bucket, zero-filled across the range.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
project_id | UUID | required | Project to query |
campaign_id | UUID | None | Scope to one campaign's resolved videos. Omit for every active tracked video in the project |
from | YYYY-MM-DD | required | Range start (inclusive, org-TZ calendar day) |
to | YYYY-MM-DD | required | Range end (inclusive) |
granularity | day | week | month | year | day | Bucket width. Weeks start on Monday |
tz | IANA tz | None | Override the org timezone used for day boundaries |
platform | tiktok | instagram | youtube | None | Only count posts on this platform (every series, including first-time creators) |
Response data array: one item per bucket, ordered by bucket_start:
{
"bucket_start": "2026-08-10",
"impressions": 456467,
"posts": 436,
"creators_posting": 39,
"new_creators": 4
}impressionsis the views gained during the bucket, attributed to the day the snapshot moved (not the video's posting day). A video's first snapshot (its pre-tracking views) is credited to its posting day when that day is inside the range.postsandcreators_postingare by posted-at date.new_creatorscounts creators whose earliest post in the scope falls in the bucket; each creator counts once over all time, not once per range.- Impressions per post is not returned; compute
impressions / postsper bucket client-side and treat buckets withposts = 0as undefined.
meta echoes from, to, tz, granularity, and campaign_id.
GET /aggregate/member-overlap#
Returns entities (creators or videos) that appear as direct members in campaigns, with the list of campaigns each belongs to. Capped at 50 rows.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
project_id | UUID | required | |
dimension | creator | video | creator | Entity type to list |
Response data array:
{
"entity_id": "uuid",
"entity_label": "handle or title",
"entity_avatar": "string | null",
"campaign_ids": ["uuid", "uuid"],
"campaign_names": ["Campaign A", "Campaign B"],
"campaign_count": 2
}Only direct membership (via campaign_items) is considered, not recursive resolution through nested campaigns.
Conversion aggregates#
Three endpoints summarise recorded conversions. All of them ignore voided conversions entirely, because a void is a retraction and never appears in a total.
Unlike the conversions list, which takes full
ISO-8601 timestamps, these take bare YYYY-MM-DD dates like the other
aggregates. to is inclusive, resolved against the organization's timezone.
GET /api/v2/analytics/aggregate/conversion-summary#
| Parameter | Type | Default | Description |
|---|---|---|---|
project_id | UUID | required | |
from | YYYY-MM-DD | None | Inclusive start |
to | YYYY-MM-DD | None | Inclusive end |
conversion_event_id | UUID | None | Restrict to one event type |
Returns zero rows when nothing was recorded in range: there is no correct currency to denominate an empty total in, so render an empty state rather than a zero.
{
"total_conversions": 128,
"total_quantity": 194,
"total_value_cents": 486000,
"currency": "USD",
"distinct_creators": 12,
"distinct_events": 3
}GET /api/v2/analytics/aggregate/conversion-leaderboard#
Creators ranked by conversion value. Accepts the summary parameters plus
limit and offset.
Rows are per (creator, currency), so a creator with conversions in two
currencies appears twice. total_count counts groups, not creators.
Totals include the full rollup: a creator's figure covers conversions recorded against their accounts and their individual videos, not just those attributed to the creator directly.
{
"creator_id": "uuid",
"display_name": "Jules",
"handle": "wyndywithjuls",
"avatar_url": "string | null",
"conversion_count": 34,
"total_quantity": 41,
"total_value_cents": 128000,
"currency": "USD",
"total_count": 12
}GET /api/v2/analytics/aggregate/conversion-timeseries#
Daily buckets in the organization's timezone, one row per (day, currency). The series is sparse: days with no conversions are omitted, so fill the spine client-side.
{ "day": "2026-07-18", "conversion_count": 9, "total_value_cents": 22500, "currency": "USD" }