Docs
Analytics API

Overview

Read attributed revenue, orders and engagement for your campaigns and automations.

The analytics API returns the figures of the Kanal Analytics page: revenue and orders attributed to WhatsApp, cost, ROAS and the engagement funnel, for the whole account or for one campaign or automation. Use it to feed an internal dashboard or a BI tool.

These endpoints require an active subscription. A team without one gets 403 { "error": "Team has no active subscription" } on every call.

Endpoints

MethodPathPurpose
GET/api/v1/stats/overviewAccount-wide KPIs
GET/api/v1/stats/chartDaily time series of one metric
GET/api/v1/stats/campaigns/{campaign_id}Stats of one campaign
GET/api/v1/stats/automations/{automation_id}Stats of one automation
GET/api/v1/campaignsList campaigns
GET/api/v1/automationsList automations

Auth and scope

Same API key as the rest of the API — Authorization: Bearer <key> (see Authentication). The key gives access to the whole Kanal account it was created in. A campaign or automation id from another account returns 404.

Attribution

Figures follow the attribution settings of your account (Settings > Attribution): the window (5 days by default), the basis (the message event an order must follow: delivered by default, read or clicked) and the timezone that cuts the days. The overview and the stats routes echo them back:

"attribution": { "basis": "delivered", "window_seconds": 432000 }

They are the same numbers as the Analytics page of the Kanal app. Attribution is rebuilt every 15 minutes, so the last half hour may still move.

Period

Every stats route takes the same query parameters:

ParamNotes
time_rangetoday, week_to_date, last_7_days, month_to_date, last_30_days (default), last_3_months, last_6_months, year_to_date, all_time, custom.
start_dateYYYY-MM-DD, required with time_range=custom and refused otherwise.
end_dateYYYY-MM-DD, required with time_range=custom and refused otherwise, not before start_date.
compared_toOptional: previous_period (same length, just before), previous_year or custom. Fills previous and changePercent in the response.
compare_start_dateYYYY-MM-DD, required with compared_to=custom and refused otherwise.
compare_end_dateYYYY-MM-DD, required with compared_to=custom and refused otherwise, not before compare_start_date.

Days are counted in your account timezone, both bounds included. The resolved period is returned in every response:

"period": { "from": "2026-11-03", "to": "2026-11-09", "timezone": "Europe/Paris" },
"compared_period": { "from": "2026-10-27", "to": "2026-11-02" }

compared_period is null without compared_to, and with all_time.

Amounts

Revenue and cost are in euros, whatever the currency of your shop.

Rate limit

60 requests / minute / API key

shared by the six routes above. Exceeding it returns 429 with a Retry-After header:

{ "error": "Too many requests. Please slow down.", "retry_after": 42 }

A dashboard that refreshes every few minutes stays far below it. Don't poll faster than every 15 minutes: the figures don't change more often.

Errors

StatusBodyCause
401{ "error": "API key is missing" } / { "error": "Invalid API key" }Auth.
403{ "error": "Team has no active subscription" }No active subscription.
404{ "error": "Not found" }Unknown campaign or automation id, or one from another account.
422{ "error": "Validation failed", "errors": { "time_range": ["…"] } }Unknown time_range, compared_to or metric, missing, malformed or unexpected date.
429{ "error": "Too many requests. Please slow down.", "retry_after": 42 }Rate limit.

On this page