Returns a campaign's results over a window: the analytics funnel (impressions,…
Returns a campaign's results over a window: the analytics funnel (impressions, clicks, conversions, revenue, visitors), the spend each channel's connector…
GET /v1/campaign/{id}/metrics
| Address | https://api.hanzo.ai/v1/campaign/{id}/metrics |
| Method | GET |
| Operation | get_campaign_by_id_metrics |
| Auth | Authorization: Bearer $HANZO_API_KEY |
Returns a campaign's results over a window: the analytics funnel (impressions, clicks, conversions, revenue, visitors), the spend each channel's connector reports, and the derived growth KPIs — CTR, CVR, CAC and ROAS.
There is exactly ONE metrics plane and nothing is stored here: the funnel is an analytics query over the campaign's utm_campaign-tagged events, and the spend is each provider's own number read through the org's connector. A warehouse that is not emitting yet degrades to available:false with zeroes — honest-empty, never a 500 and never a fabricated number. When the campaign runs more than one creative and an experiment is wired, abTest carries the A/B analysis.
Request
4 fields.
| Field | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | ID is the campaign to report on, from the path. |
range | query | string | — | Range is the lookback window: 24h, 7d, 30d or 90d. |
start | query | string | — | Start is an explicit RFC3339 window start. |
end | query | string | — | End is an explicit RFC3339 window end. |
Response
| Status | Body | Meaning |
|---|---|---|
200 | campaignResults | ok |
200 body — 26 fields.
| Field | In | Type | Always | Description |
|---|---|---|---|---|
abTest | body | any | — | ABTest is the creative A/B analysis from the experiments primitive (experiment.Analyze, pull-model), present only when the campaign runs more than one creative… |
available | body | boolean | — | Available is false when the analytics warehouse is not connected or the query failed: the funnel below is then zero because nothing could be read, not because… |
cac | body | number | — | CAC is customer acquisition cost: spend DOLLARS per conversion, rounded to cents. |
campaignId | body | string | — | CampaignID is the campaign these results are for, echoed from the request. |
channels | body | ChannelMetric[] | — | Channels is the per-channel spend breakdown that SpendCents sums, one row per channel on the campaign including the ones that never launched. |
channels[].externalId | body | string | — | ExternalID is the provider-side id of the execution the spend belongs to. |
channels[].kind | body | string | — | Kind is which channel this row is: paid, organic or email. |
channels[].platform | body | string | — | Platform is the provider the spend was read from: meta, google, x, instagram, or the email provider. |
channels[].spendCents | body | integer | — | SpendCents is what the provider itself reports this channel spent, in CENTS. |
channels[].spendError | body | string | — | SpendError is why this channel's spend could not be read (connector not connected, provider error), as one secret-free line. |
channels[].status | body | string | — | Status is the channel's launch state on the campaign — pending, live, paused, failed or unavailable. |
clicks | body | integer | — | Clicks is the campaign's click events over the window. |
conversions | body | integer | — | Conversions is the terminal funnel events attributed to the campaign — orders completed, signups completed, explicit conversion events. |
ctr | body | number | — | CTR is clicks per impression, a fraction rounded to 4 places (0.0123 = 1.23%), not a percentage. |
cvr | body | number | — | CVR is conversions per click, a fraction rounded to 4 places. |
end | body | string | — | End is the window's end, RFC3339 UTC — the read's own clock unless an explicit pair was given. |
impressions | body | integer | — | Impressions is how many times the campaign's creatives were shown, counted from its utm_campaign-tagged impression events. |
name | body | string | — | Name is the campaign's display name at read time, so a result can be labelled without a second fetch. |
range | body | string | — | Range is the window actually used: 24h, 7d, 30d, 90d, or "custom" when an explicit start/end pair was honored. |
revenue | body | number | — | Revenue is the summed revenue attribute of the campaign's events, in whole CURRENCY UNITS (dollars) — the one money value here that is not in cents. |
roas | body | number | — | ROAS is return on ad spend: revenue per spend DOLLAR, rounded to 2 places (2.5 = 1). |
source | body | string | — | Source names the analytics table the funnel was read from, so an operator can see exactly what was counted. |
spendCents | body | integer | — | SpendCents is the campaign's total spend in CENTS: the sum of what each live channel's provider reports. |
start | body | string | — | Start is the window's inclusive start, RFC3339 UTC. |
status | body | string | — | Status is the campaign's lifecycle state at read time — draft, live, paused, completed or failed. |
visitors | body | integer | — | Visitors is how many distinct people the campaign reached, counted by event identity across ALL its events in the window — not a subset of Impressions, so it… |
Failure carries the platform error shape — see Errors.
Examples
hanzo campaign metrics <id>import { Configuration, CampaignApi } from 'hanzoai';
const api = new CampaignApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.getCampaignByIdMetrics({ id: 'id' });from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import CampaignApi
client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = CampaignApi(client).get_campaign_by_id_metrics(id='id')cfg := cloud.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := cloud.NewAPIClient(cfg)
resp, _, err := client.CampaignAPI.GetCampaignByIdMetrics(context.Background()).Execute()
if err != nil {
return err
}use hanzo_cloud::apis::{configuration::Configuration, campaign_api};
let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();
let result = campaign_api::get_campaign_by_id_metrics(&cfg, Default::default()).await?;import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.CampaignApi;
ApiClient client = new ApiClient();
client.setRequestInterceptor(b -> b.header("Authorization", "Bearer " + System.getenv("HANZO_API_KEY")));
var result = new CampaignApi(client).getCampaignByIdMetrics();curl https://api.hanzo.ai/v1/campaign/<id>/metrics \
-H "Authorization: Bearer $HANZO_API_KEY"The door reaches campaign through the campaign tool, which names its 11 operations with its own verbs — this one among them, under a name only the door declares. describe explains any of them:
curl -X POST https://api.hanzo.ai/v1/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "describe",
"arguments": {
"op": "get_campaign"
}
}
}'How is this guide?