Skip to main content

GET /analytics/campaigns

Returns aggregated UTM campaign performance for the active Organizational Unit. Each row in the response represents a unique UTM value (campaign, source, or medium) discovered from the metadata of ingested engagement events. Use this endpoint to power campaign performance dashboards, validate channel ROI, or pull attribution signals into your own BI stack.

When to Use This

  • Channel performance reporting: pull weekly or monthly campaign rollups into a BI tool or shared dashboard.
  • Attribution review: see which campaigns, sources, or mediums are tied to MQLs, pipeline, and closed-won revenue.
  • Tagging hygiene audits: list every distinct UTM value to spot typos or casing drift in your campaign tagging.
  • Lead drilldown automation: combine the aggregate endpoint with the lead drilldown endpoint to pull contact-level detail for a specific campaign.

Authentication

Required: Yes. Use a Personal Access Token or a Service Account credential. All results are scoped to the active Organizational Unit.

Request

Endpoint: GET /analytics/campaigns

Headers:

Accept: application/json

Query Parameters

ParameterTypeRequiredDescription
group_bystringNoOne of campaign, source, or medium. Selects which UTM key to aggregate on (utm_campaign, utm_source, or utm_medium). Defaults to campaign. Unknown values fall back to campaign.
start_datestringNoLower bound for event occurred_at. Accepts YYYY-MM-DD or RFC3339. Omit for no lower bound.
end_datestringNoUpper bound for event occurred_at. Accepts YYYY-MM-DD or RFC3339. Omit for no upper bound.

Example:

curl -u "CLIENT_ID:CLIENT_SECRET" \
-H "Accept: application/json" \
"https://your-instance.com/analytics/campaigns?group_by=campaign&start_date=2026-03-01&end_date=2026-03-31"

Response

Success Response

Status: 200 OK

The response is an envelope containing the requested grouping and an array of campaign rows, sorted by total event count descending.

FieldTypeDescription
group_bystringThe grouping that was applied: campaign, source, or medium.
campaignsarrayOne row per unique UTM value with at least one matching event in the requested window. Empty array when no events match.

Each entry in campaigns has the following fields:

FieldTypeDescription
namestringThe UTM value (e.g. spring-2026-launch, linkedin, email).
total_eventsintegerCount of events whose metadata contains the chosen UTM key. Counts only the canonical event in a normalized pair (duplicates are excluded).
unique_leadsintegerDistinct leads with at least one event tagged to this UTM value.
mql_countintegerDistinct leads whose current engagement_level is Warm or Hot. This is a current snapshot, not historical — leads who have since cooled off are excluded.
total_pointsintegerSum of metadata.score_delta across the campaign's events. Events that did not trigger scoring contribute 0.
deals_countintegerDistinct deals reachable from any of the campaign's leads via the lead → account → deal join. Requires the lead to have an account_id.
pipeline_valuenumberSum of distinct deal amount values across all deals reachable from the campaign's leads, regardless of stage. Multi-touch and unweighted: the same deal counts in full toward every campaign that touched the account.
closed_won_valuenumberSubset of pipeline_value filtered to deals whose stage matches closedwon or closed_won (case-insensitive). Same multi-touch caveat applies.
first_event_atstring or nullRFC3339 timestamp of the earliest event for this campaign, or null when no events fall in the window.
last_event_atstring or nullRFC3339 timestamp of the most recent event for this campaign, or null when no events fall in the window.

Example Response:

{
"group_by": "campaign",
"campaigns": [
{
"name": "spring-2026-launch",
"total_events": 1284,
"unique_leads": 312,
"mql_count": 47,
"total_points": 6420,
"deals_count": 12,
"pipeline_value": 485000,
"closed_won_value": 120000,
"first_event_at": "2026-03-01T08:14:22Z",
"last_event_at": "2026-03-31T19:42:01Z"
},
{
"name": "evergreen-content",
"total_events": 642,
"unique_leads": 198,
"mql_count": 18,
"total_points": 2310,
"deals_count": 4,
"pipeline_value": 95000,
"closed_won_value": 0,
"first_event_at": "2026-03-02T11:01:08Z",
"last_event_at": "2026-03-30T22:17:55Z"
}
]
}

Common Errors

StatusMeaningSolution
401UnauthorizedCheck your API credentials.
403ForbiddenEnsure your account has access to analytics data for the active Organizational Unit.
500Server ErrorRetry later or contact support if the issue persists.

GET /analytics/campaigns/{campaign_name}/leads

Returns the individual leads attributed to a single UTM value. Use this when you have a campaign of interest from the aggregate endpoint and want to pull contact-level detail for outreach, QA, or reporting.

The response is capped at 500 leads, ordered by earliest first touch (most recent first).

Authentication

Required: Yes. Same as the aggregate endpoint.

Request

Endpoint: GET /analytics/campaigns/{campaign_name}/leads

Path ParameterTypeRequiredDescription
campaign_namestringYesThe exact UTM value to look up. URL-encode if it contains spaces or special characters.
Query ParameterTypeRequiredDescription
group_bystringNoOne of campaign, source, or medium. Selects which UTM key to match campaign_name against. Defaults to campaign. Must match the grouping used to discover the value.
start_datestringNoLower bound for event occurred_at. Accepts YYYY-MM-DD or RFC3339.
end_datestringNoUpper bound for event occurred_at. Accepts YYYY-MM-DD or RFC3339.

Example:

curl -u "CLIENT_ID:CLIENT_SECRET" \
-H "Accept: application/json" \
"https://your-instance.com/analytics/campaigns/spring-2026-launch/leads?group_by=campaign&start_date=2026-03-01"

Response

Status: 200 OK

FieldTypeDescription
campaign_namestringEchoes the path parameter so the caller can confirm what was queried.
leadsarrayLead records attributed to the campaign. Up to 500 entries. Empty array when no leads match.

Each entry in leads has the following fields:

FieldTypeDescription
lead_idstringUUID of the lead.
display_namestring or nullBest display value for the lead, derived from email, phone, social, external ID, or cookie alias in that order.
emailstring or nullPrimary email alias when the lead has one.
first_namestring or nullLead's first name from metadata (first_name or firstname).
last_namestring or nullLead's last name from metadata (last_name or lastname).
titlestring or nullJob title from lead metadata.
companystring or nullCompany name from lead metadata.
scoreintegerTotal engagement score for the lead. Defaults to 0 when no score has been calculated.
levelstring or nullCurrent engagement level (Cold, Warm, Hot, etc.).
account_idstring or nullUUID of the lead's associated account, when one is matched.
account_namestring or nullDisplay name of the matched account.
deal_stagestring or nullStage of the most recent associated deal, when an account-linked deal exists.
deal_amountnumber or nullAmount of the most recent associated deal.
first_touch_atstring or nullRFC3339 timestamp of this lead's earliest event tagged to the campaign.
last_touch_atstring or nullRFC3339 timestamp of this lead's most recent event tagged to the campaign.

Example Response:

{
"campaign_name": "spring-2026-launch",
"leads": [
{
"lead_id": "9b1a72c2-2d2c-4d8b-9a5e-2e3f4a1b9c10",
"display_name": "rachel.kim@example.com",
"email": "rachel.kim@example.com",
"first_name": "Rachel",
"last_name": "Kim",
"title": "Director of Demand Generation",
"company": "Acme Co",
"score": 78,
"level": "Hot",
"account_id": "0f3c4d5e-6789-4abc-9def-0123456789ab",
"account_name": "Acme Co",
"deal_stage": "negotiation",
"deal_amount": 75000,
"first_touch_at": "2026-03-04T13:21:08Z",
"last_touch_at": "2026-03-31T17:05:42Z"
}
]
}

Common Errors

StatusMeaningSolution
400campaign_name is requiredPass a non-empty value in the path.
401UnauthorizedCheck your API credentials.
403ForbiddenEnsure your account has access to analytics data for the active Organizational Unit.
500Server ErrorRetry later or contact support if the issue persists.

Important Notes

Auto-Discovery From UTMs

Campaigns are not stored as standalone records — they are discovered at query time from the utm_campaign, utm_source, or utm_medium keys in event metadata. Any unique UTM string becomes its own row. Casing differences (Spring2026 vs spring2026) and whitespace differences create separate campaigns. Cleanup must happen upstream in the link generators producing the events; kenbun does not offer rename, alias, or merge for campaigns.

Deduplication Of Normalized Events

The aggregator filters out events where normalized = true AND normalized_parent = false, so a normalized event pair counts only once (the canonical row). Events that are not part of a normalization pair are counted as-is.

MQL Is Current, Not Historical

mql_count reflects each lead's current engagement_level, not their level at the time of the campaign event. A lead who was Hot during the campaign and has since cooled to Cold will not be counted. This makes the MQL number useful for "how many leads from this campaign are still warm?" but not for "how many leads did this campaign ever heat up?"

Multi-Touch, Unweighted Pipeline

pipeline_value and closed_won_value are computed by joining each campaign's leads to their accounts and then to deals on those accounts. The full deal amount is credited to every campaign that touched the account. Two campaigns that each generated one event for the same account both show the deal's full amount. Summing pipeline columns across rows will overcount; treat each row as "deals this campaign was involved in" rather than a clean attribution slice.

Pipeline Requires Account Linkage

Pipeline metrics flow through the lead → account → deal join. Leads without an account_id contribute to total_events, unique_leads, and mql_count but never to deals_count, pipeline_value, or closed_won_value.

Organizational Unit Scoping

All results are scoped to the active Organizational Unit determined by the request context (typically the active-OU cookie set by POST /org-units/active). Events tagged to the same UTM value in different OUs do not aggregate together.

See Also