Campaigns
Campaign performance, ROAS, ad-level breakdowns, and daily spend trends.
Campaigns
Top Campaigns
GET /api/v1/campaignsReturns top-performing campaigns ranked by attributed outcome value, with ROAS
and spend data. Pass level to drill down to ad sets or individual ads.
Scope: campaigns:read
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
date_from | string | Yes | Start date in YYYY-MM-DD format. |
date_to | string | Yes | End date in YYYY-MM-DD format. |
model | string | No | Attribution model. Default last_touch. Options: first_touch, linear, position_based, time_decay, last_non_direct. |
limit | number | No | Maximum results. Default 10, max 100. |
level | string | No | Hierarchy level: campaign, ad_set, or ad. When set, returns detailed performance fields. |
Request
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
"https://www.atribu.app/api/v1/campaigns?date_from=2026-03-01&date_to=2026-03-25&model=last_touch&limit=5"const res = await fetch(
"https://www.atribu.app/api/v1/campaigns?date_from=2026-03-01&date_to=2026-03-25&model=last_touch&limit=5",
{ headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data, meta } = await res.json();import requests
res = requests.get(
"https://www.atribu.app/api/v1/campaigns",
headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
params={
"date_from": "2026-03-01",
"date_to": "2026-03-25",
"model": "last_touch",
"limit": 5,
},
)
data = res.json()["data"]Response
{
"data": [
{
"campaign_id": "uuid",
"campaign_name": "Spring Sale - Conversions",
"spend": 1800.00,
"outcome_count": 45,
"outcome_value": 6200.00,
"roas": 3.44
}
],
"meta": {
"date_from": "2026-03-01",
"date_to": "2026-03-25",
"profile_id": "uuid"
}
}Response fields
| Field | Type | Description |
|---|---|---|
campaign_id | string | Internal campaign UUID. |
campaign_name | string | Human-readable campaign name from the ad platform. |
spend | number | Total ad spend in your reporting currency. |
outcome_count | number | Number of attributed conversions. |
outcome_value | number | Total attributed cash revenue. |
roas | number | Return on ad spend (outcome_value / spend). |
Ad-level detail
Pass level=ad to get granular performance per ad creative. This adds
additional fields to each row.
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
"https://www.atribu.app/api/v1/campaigns?date_from=2026-03-01&date_to=2026-03-25&level=ad"const res = await fetch(
"https://www.atribu.app/api/v1/campaigns?date_from=2026-03-01&date_to=2026-03-25&level=ad",
{ headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();res = requests.get(
"https://www.atribu.app/api/v1/campaigns",
headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
params={"date_from": "2026-03-01", "date_to": "2026-03-25", "level": "ad"},
)
data = res.json()["data"]Additional fields when level is set
| Field | Type | Description |
|---|---|---|
entity_level | string | The level of this row (campaign, ad_set, ad). |
parent_name | string | Name of the parent entity (campaign or ad set). |
impressions | number | Total impressions served. |
clicks | number | Total clicks. |
reach | number | Unique accounts reached. |
ctr | number | Click-through rate (%). |
avg_cpm | number | Average cost per 1,000 impressions. |
avg_cpc | number | Average cost per click. |
cac | number | Customer acquisition cost (spend / outcome_count). |
status | string | Entity status (ACTIVE, PAUSED, etc.). |
objective | string | Campaign objective. |
daily_budget | number | Daily budget if set on the platform. |
Campaign Trend
GET /api/v1/campaigns/trendReturns daily spend, impressions, and clicks for up to 10 specific campaigns, ad sets, or ads. Use this to render sparklines or compare entity performance over time.
Scope: campaigns:read
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
date_from | string | Yes | Start date in YYYY-MM-DD format. |
date_to | string | Yes | End date in YYYY-MM-DD format. |
entity_ids | string | Yes | Comma-separated list of entity UUIDs (max 10). |
level | string | No | campaign, ad_set, or ad. Default campaign. |
Request
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
"https://www.atribu.app/api/v1/campaigns/trend?date_from=2026-03-20&date_to=2026-03-25&entity_ids=uuid1,uuid2&level=campaign"const ids = ["uuid1", "uuid2"].join(",");
const res = await fetch(
`https://www.atribu.app/api/v1/campaigns/trend?date_from=2026-03-20&date_to=2026-03-25&entity_ids=${ids}&level=campaign`,
{ headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();import requests
res = requests.get(
"https://www.atribu.app/api/v1/campaigns/trend",
headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
params={
"date_from": "2026-03-20",
"date_to": "2026-03-25",
"entity_ids": "uuid1,uuid2",
"level": "campaign",
},
)
data = res.json()["data"]Response
{
"data": [
{ "entity_id": "uuid1", "date": "2026-03-20", "spend": 120.00, "impressions": 15000, "clicks": 340 },
{ "entity_id": "uuid1", "date": "2026-03-21", "spend": 135.00, "impressions": 16200, "clicks": 380 },
{ "entity_id": "uuid2", "date": "2026-03-20", "spend": 95.00, "impressions": 11800, "clicks": 275 }
],
"meta": {
"date_from": "2026-03-20",
"date_to": "2026-03-25",
"profile_id": "uuid"
}
}Response fields
| Field | Type | Description |
|---|---|---|
entity_id | string | The campaign, ad set, or ad UUID. |
date | string | Calendar date (YYYY-MM-DD). |
spend | number | Ad spend for that entity on that day. |
impressions | number | Impressions served. |
clicks | number | Clicks recorded. |
Entity ID format
entity_ids expects Atribu internal UUIDs (from the campaign_id field in
the Top Campaigns response), not platform-specific IDs. You can pass up to
10 IDs in a single request.
Top Performers
GET /api/v1/top-performersThe profile's best-performing ads, scored against comparable creative — a cohort = channel × format × objective × audience warmth × geo × placement. The score is cohort-normalized, empirical-Bayes-smoothed (a small-sample ad shrinks toward the cohort mean — it lands mid-pack, the honest "we don't know yet"), and maturity-staged. So a $500/mo clinic and a $30k/mo clinic are ranked apples-to-apples.
Each ad carries three distinct measures — never merge them:
composite_score(0–100) — a transparent rule-based blend of the ad's within-cohort percentiles across funnel layers. The headline rank.top_performer_likelihood(0–1) — a probability: a likelihood, not a guarantee. Whenscore_sourceismodelit's the calibrated output of a LambdaMART creative ranker (model_versiontells you which model); otherwise (score_sourceisrules) it's a monotone function ofcomposite_score. It is not ROAS and not lift.attributed_revenue/roas(andattributed_outcomes_total/attributed_pipeline_outcomes/attributed_pipeline_value) — real Atribu-attributed outcomes.truth_grade='attributed'means Atribu deterministically attributed ≥1 outcome of any kind to this ad (cash payments or pipeline conversions — leads, appointments, closed-won).meta_reported_conversions/meta_reported_conversion_valueare a separate, lower-confidence signal — Meta's own last-click count for the ad, not Atribu's chain; they never settruth_grade.
primary_outcome_kind ∈ cash · pipeline · messaging · meta · none tells you which of
the above to headline for a given ad (a lead-gen ad shows pipeline outcomes, a Messages ad shows
conversations started, an ad with only Meta-reported conversions shows meta_reported_conversions
labeled as Meta's attribution, none = no attributed outcome yet → use composite_score).
maturity_stage (cold → early → mature → calibrated) says how settled
the score is — act on mature/calibrated, treat cold as provisional.
reason_codes explain why an ad ranks (which funnel layer is strong or weak,
each with a sample_confidence band). Scores refresh on a daily cadence.
Scope: campaigns:read
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
window | string | No | Rolling lookback: 7d, 14d, 28d (default), or lifetime. Not a custom date range. |
limit | number | No | Maximum ads. Default 10, max 50. |
min_spend | number | No | Only ads with at least this much spend in the window. |
objectives | string | No | Comma-separated cohort objectives to keep (messaging, sales, leads, traffic, awareness). |
truth_grades | string | No | Comma-separated: predicted, attributed, lift. |
outcome_kinds | string | No | Comma-separated headline-outcome kinds to keep: cash, pipeline, messaging, meta, none. |
formats | string | No | Comma-separated creative formats (ugc_video, talking_head, static, carousel, …). |
has_video | boolean | No | true to keep only ads with a video creative; false for only static. |
Request
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
"https://www.atribu.app/api/v1/top-performers?window=28d&limit=5&truth_grades=attributed"Response
{
"data": [
{
"ad_external_id": "120210000000000123",
"ad_name": "UGC — before/after — DM offer",
"campaign_name": "Always-on — Messaging",
"ad_set_name": "Prospecting — broad",
"creative_thumbnail_url": "https://…",
"video_id": "987654321",
"score_window": "28d",
"composite_score": 78,
"top_performer_likelihood": 0.81,
"model_version": 3,
"score_source": "model",
"maturity_stage": "mature",
"truth_grade": "attributed",
"primary_outcome_kind": "messaging",
"cohort_key": "meta|ugc_video|messaging|prospecting|geo_any|advantage",
"cohort_objective": "messaging",
"cohort_format": "ugc_video",
"cohort_audience_warmth": "prospecting",
"cohort_n_ads": 17,
"hook_type": "before_after",
"creative_format": "ugc_video",
"cta_type": "send_dm",
"primary_angle": "fast visible results",
"offers": ["free consultation"],
"spend": 4120.0,
"impressions": 512000,
"ctr": 1.42,
"cpc": 0.57,
"attributed_cash_outcomes": 6,
"attributed_revenue": 9200.0,
"roas": 2.23,
"cac": 686.67,
"attributed_outcomes_total": 18,
"attributed_pipeline_outcomes": 12,
"attributed_pipeline_value": 6400.0,
"meta_reported_conversions": 0,
"meta_reported_conversion_value": 0.0,
"cost_per_conversation": 0.33,
"messaging_conversations_started": 412,
"first_reply_rate": 41.2,
"avg_engagement_score": null,
"reason_codes": [
{ "layer": "attention", "polarity": "strength", "percentile": 88, "sample_confidence": "high" },
{ "layer": "postclick_messaging", "polarity": "strength", "percentile": 91, "sample_confidence": "high" },
{ "layer": "attributed_revenue", "polarity": "strength", "percentile": 74, "sample_confidence": "medium" }
]
}
],
"meta": {
"profile_id": "uuid"
}
}Response fields
| Field | Type | Description |
|---|---|---|
ad_external_id | string | Platform ad id (e.g. Meta ad id). |
composite_score | number | 0–100, the cohort-normalized rule blend. null until the ad has been scored. The headline rank. |
top_performer_likelihood | number | 0–1 probability — a likelihood, not a guarantee. Not ROAS, not lift — see the note above. |
model_version | number | The ML ranker version that scored this ad, or null when score_source is rules. |
score_source | string | model (the ML ranker produced the likelihood) or rules (it fell back to composite_score). |
maturity_stage | string | cold · early · mature · calibrated — how settled the score is. |
truth_grade | string | predicted (proxy signals only) · attributed (≥1 Atribu-attributed outcome of any kind) · lift (experiment-validated). |
primary_outcome_kind | string | The headline outcome to show: cash · pipeline · messaging · meta (Meta's own attribution) · none. |
cohort_* | string | The comparison cohort this ad was scored within, plus cohort_n_ads (cohort size). |
hook_type / creative_format / cta_type / primary_angle / offers | mixed | Creative DNA — what to replicate. |
attributed_revenue / roas / attributed_cash_outcomes / cac | number | Atribu-attributed cash (null/0 when none). |
attributed_outcomes_total / attributed_pipeline_outcomes / attributed_pipeline_value | number | All Atribu-attributed conversions (any type); the pipeline subset (leads/appointments/closed-won) + its projected value (not cash). |
meta_reported_conversions / meta_reported_conversion_value | number | Meta's own reported per-ad conversions (last-click — not Atribu's chain; never sets truth_grade). |
cost_per_conversation / messaging_conversations_started / first_reply_rate | number | Messaging-objective signals (the "cheap conversations" lens). |
reason_codes | array | { layer, polarity: strength|weakness, percentile, sample_confidence: low|medium|high } — why it ranks. |
Workspace-wide view
This endpoint is scoped to the API key's profile. The agency-wide
cross-profile Top Performers (all clients in one ranked list, plus recurring
creative patterns) is available through the Atribu MCP server —
top_workspace_performers. See the MCP docs.
Scores build on a daily cadence
Top-performer scores rebuild once a day (independent of the latency-sensitive
attribution recompute). A freshly connected Meta account needs one sync cycle
before its ads appear here. An empty data array usually means "not scored
yet", not "no top performers".