Insights API
Run analytics queries programmatically. The same query spec powers dashboard Insights, Overview KPIs, and dashboard tiles.
Service: InsightsService
Auth: Shared API key or JWT
Query
Method: InsightsService.Query
Query spec structure
{
"query": {
"events": [
{ "name": "page_view" }
],
"timeRange": {
"start": "2026-05-01T00:00:00Z",
"end": "2026-06-01T00:00:00Z"
},
"granularity": "GRANULARITY_DAY",
"aggregation": "AGGREGATION_UNIQUE_USERS",
"breakdown": {
"property": "$geo.country"
},
"filters": [
{
"property": "plan",
"operator": "OPERATOR_EQUALS",
"value": "pro"
}
]
}
}Query fields
| Field | Description |
|---|---|
events | One or more event names; optional per-event filters |
timeRange.start/end | ISO 8601 timestamps; boundaries use project timezone |
granularity | HOUR, DAY, WEEK |
aggregation | COUNT, UNIQUE_USERS, SUM, AVERAGE |
aggregationProperty | Required for SUM/AVERAGE (e.g. revenue) |
breakdown.property | Property to group by (e.g. $geo.country, plan) |
filters | Global property filters applied to all events |
conversionWindow | Duration for funnel queries (e.g. 604800s = 7 days) |
Example: Daily active users
import { InsightsService } from './gen/insights/v1/insights_connect'
const client = createClient(InsightsService, sharedTransport)
const result = await client.query({
query: {
events: [{ name: 'page_view' }],
timeRange: {
start: new Date('2026-05-01'),
end: new Date('2026-06-01')
},
granularity: 'GRANULARITY_DAY',
aggregation: 'AGGREGATION_UNIQUE_USERS'
}
})
// result.series = [{ timestamp, value }, ...]Example: Revenue by country
{
"query": {
"events": [{ "name": "purchase" }],
"timeRange": { "start": "2026-05-01T00:00:00Z", "end": "2026-06-01T00:00:00Z" },
"granularity": "GRANULARITY_DAY",
"aggregation": "AGGREGATION_SUM",
"aggregationProperty": "revenue",
"breakdown": { "property": "$geo.country" }
}
}Example: Signup funnel
{
"query": {
"events": [
{ "name": "signup_started" },
{ "name": "signup_completed" },
{ "name": "purchase" }
],
"timeRange": { "start": "2026-05-01T00:00:00Z", "end": "2026-06-01T00:00:00Z" },
"visualization": "VISUALIZATION_FUNNEL",
"conversionWindow": "604800s"
}
}Response includes step counts and conversion rates:
{
"funnel": {
"steps": [
{ "event": "signup_started", "count": 1000, "conversionRate": 1.0 },
{ "event": "signup_completed", "count": 650, "conversionRate": 0.65 },
{ "event": "purchase", "count": 120, "conversionRate": 0.12 }
]
}
}Example: Retention
{
"query": {
"visualization": "VISUALIZATION_RETENTION",
"startingEvent": { "name": "signup_completed" },
"returnEvent": { "name": "page_view" },
"timeRange": { "start": "2026-01-01T00:00:00Z", "end": "2026-06-01T00:00:00Z" },
"granularity": "GRANULARITY_WEEK"
}
}Response shapes
| Visualization | Response field | Contains |
|---|---|---|
| Line / Area / Bar | series[] | { timestamp, value, breakdown? } |
| Funnel | funnel.steps[] | { event, count, conversionRate } |
| Retention | retention.cohorts[] | { cohortDate, periods[] } |
| Table | rows[] | { dimensions[], value } |
Exact field names are defined in the protobuf schema. Generate clients with buf generate.
TypeScript client setup
import { createClient } from '@connectrpc/connect'
import { createConnectTransport } from '@connectrpc/connect-web'
import { InsightsService } from './gen/insights/v1/insights_connect'
const transport = createConnectTransport({
baseUrl: 'https://api.pug.sh',
interceptors: [
next => async req => {
req.header.set('Authorization', `Bearer ${process.env.PUG_SHARED_API_KEY}`)
req.header.set('x-project-id', process.env.PUG_PROJECT_ID!)
return next(req)
}
]
})
const client = createClient(InsightsService, transport)Use a shared API key for server-side scripts. Never expose it in client-side code.
Dashboard equivalents
| API query | Dashboard location |
|---|---|
| Line chart | Insights |
| KPI tile | Overview |
| Saved chart | Dashboards |
Build queries in the dashboard first, then copy the query spec for API use.
ClickHouse
Insights queries run against ClickHouse. Query performance depends on time range, event volume, and breakdown cardinality. Very high-cardinality breakdowns (e.g. user ID) may be slow or truncated.
See backend architecture docs in the cotton repo.
Further reading
Last updated on