Analytics & Insights API
Read endpoints over the delivery, engagement, and conversion data the platform records for your workspace: campaign and journey funnels, attribution rollups, a best-time-to-send heatmap, holdout lift readouts, and chart annotations. Conversion tracking is configured through the same surface.
These endpoints are authenticated with a bearer token:
Authorization: Bearer YOUR_ACCESS_TOKEN
Timestamps are RFC 3339. Where a start / end window is accepted, both are
optional and the window defaults to the last 30 days.
GET /api/v1/insights/funnel
Returns the engagement funnel for one campaign or journey: how many messages were sent, delivered, opened, clicked, and how many conversions (with revenue) were attributed back to it.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
source_type | string | Yes | campaign or journey. |
source_id | string | Yes | UUID of the campaign or journey (the ID, not the name). |
start | RFC 3339 | No | Window start. Defaults to 30 days ago. |
end | RFC 3339 | No | Window end. Defaults to now. |
Response — 200 OK
{
"sent": 12000,
"delivered": 11800,
"opened": 4100,
"clicked": 950,
"converted": 120,
"revenue_cents": 458800
}
422 — missing or invalid source_type, or source_id is not a valid UUID.
GET /api/v1/insights/summary
Attribution summary grouped by campaign, journey, or channel: conversions and attributed revenue per source over the window.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
group_by | string | Yes | campaign, journey, or channel. |
start | RFC 3339 | No | Window start. Defaults to 30 days ago. |
end | RFC 3339 | No | Window end. Defaults to now. |
Response — 200 OK
{
"rows": [
{
"source_id": "9c4e1f2a-7b6d-4c3e-8a1f-2b3c4d5e6f70",
"conversions": 120,
"revenue_cents": 458800
}
]
}
422 — missing or invalid group_by.
GET /api/v1/insights/heatmap
Best-time-to-send heatmap: engagement bucketed by weekday and hour (UTC) over
a trailing window. Empty buckets are omitted from cells; fill the 7 × 24
grid client-side (weekday 0 = Sunday … 6 = Saturday, hour 0–23).
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
days | int | No | Trailing window in days. Defaults to 90, capped at 365. |
Response — 200 OK
{
"window_days": 90,
"cells": [
{ "weekday": 2, "hour": 9, "delivered": 1800, "opened": 640 }
]
}
GET /api/v1/insights/lift
Treated-versus-holdout lift readout for a send that was created with a holdout percentage. Compares the conversion rate of subscribers who received the message against the held-out control group.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | The notification job ID of the send. |
Response — 200 OK
{
"job_id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
"treated": {
"size": 9500,
"converters": 240,
"conversion_rate": 0.0253,
"revenue_cents": 912000
},
"holdout": {
"size": 500,
"converters": 6,
"conversion_rate": 0.012,
"revenue_cents": 22800
},
"lift_percentage_points": 1.33,
"window_days": 7,
"conversion_configured": true
}
| Field | Type | Description |
|---|---|---|
treated / holdout | object | Group size, converters within the attribution window, derived conversion_rate (0 when the group is empty), and conversion revenue. |
lift_percentage_points | number | Treated conversion rate minus holdout rate, in percentage points. |
window_days | number | The attribution window applied. |
conversion_configured | boolean | false when the workspace has no conversion events configured — the readout is vacuously zero; configure a conversion event first. |
422 — missing job_id.
Conversion configuration
Attribution needs to know which behavioral events count as conversions. Definitions apply workspace-wide.
GET /api/v1/insights/config
Response — 200 OK
{
"definitions": [
{
"event_name": "purchase",
"value_property": "total_cents",
"window_seconds": 604800
}
]
}
POST /api/v1/insights/config
Creates or updates a conversion event definition (upsert by event_name).
Request Body
{
"event_name": "purchase",
"value_property": "total_cents",
"window_seconds": 604800
}
| Field | Type | Required | Description |
|---|---|---|---|
event_name | string | Yes | The behavioral event name that counts as a conversion. |
value_property | string | No | Event property holding the revenue amount in cents. |
window_seconds | int | No | Attribution window after a message touch. |
Returns 204 No Content.
DELETE /api/v1/insights/config
Deletes a conversion event definition. The event name is passed as a query parameter (names may contain dots).
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
event_name | string | Yes | Name of the definition to delete. |
Returns 204 No Content, or 404 if no definition with that name exists.
Chart annotations
Annotations are timestamped notes that render on analytics charts — some are recorded automatically by the platform (for example plan changes), and you can create your own.
The annotation object
| Field | Type | Description |
|---|---|---|
id | string | Annotation UUID. |
occurred_at | string | When the annotated moment happened. |
ends_at | string | Optional range end; absent for point annotations. |
text | string | The note text. |
source | string | auto (platform-recorded) or manual (user-created). |
kind | string | Annotation kind; manual annotations are always custom. |
visibility | string | tenant or agency_only. |
ref_id | string | Optional reference to the related object. |
created_by | string | User who created a manual annotation. |
created_at | string | Creation timestamp. |
GET /api/v1/annotations
Lists annotations overlapping a time range.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
from | RFC 3339 | Yes | Range start. |
to | RFC 3339 | Yes | Range end; must not precede from. |
kinds | string | No | Comma-separated kind filter, e.g. custom,plan_change. |
Response — 200 OK
{
"annotations": [
{
"id": "0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"occurred_at": "2026-07-20T09:00:00Z",
"text": "Summer campaign launch",
"source": "manual",
"kind": "custom",
"visibility": "tenant",
"created_at": "2026-07-20T09:05:00Z"
}
]
}
422 — from / to missing, not RFC 3339, or to precedes from.
POST /api/v1/annotations
Creates a manual annotation (kind is always custom).
Request Body
{
"occurred_at": "2026-07-20T09:00:00Z",
"ends_at": "2026-07-21T09:00:00Z",
"text": "Summer campaign launch",
"visibility": "tenant"
}
| Field | Type | Required | Description |
|---|---|---|---|
occurred_at | RFC 3339 | Yes | When the annotated moment happened. |
ends_at | RFC 3339 | No | Range end for range annotations. |
text | string | Yes | Note text, up to 500 characters. |
visibility | string | No | Defaults to tenant. agency_only may only be set from an agency session operating on a client workspace. |
Returns 201 with the created annotation object. 403 — read-only session,
or agency_only requested outside an agency session.
DELETE /api/v1/annotations/:id
Deletes a manual annotation. Platform-recorded (source: "auto")
annotations cannot be deleted.
Returns 204 No Content, or 404 if the annotation does not exist.