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
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/stats/overview | Account-wide KPIs |
GET | /api/v1/stats/chart | Daily 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/campaigns | List campaigns |
GET | /api/v1/automations | List 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:
| Param | Notes |
|---|---|
time_range | today, 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_date | YYYY-MM-DD, required with time_range=custom and refused otherwise. |
end_date | YYYY-MM-DD, required with time_range=custom and refused otherwise, not before start_date. |
compared_to | Optional: previous_period (same length, just before), previous_year or custom. Fills previous and changePercent in the response. |
compare_start_date | YYYY-MM-DD, required with compared_to=custom and refused otherwise. |
compare_end_date | YYYY-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 keyshared 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
| Status | Body | Cause |
|---|---|---|
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. |