Skip to content

Signals Analytics

Four POST endpoints answer every “how is the site doing” question. They share one request vocabulary: a period, optional clauses that narrow the sessions, and for graphs a metric. All four count real users only; bot sessions are excluded.

MethodPathScopeReturns
POST/v2/property/signals/analytics/metricssignals:readSummary cards with period-over-period deltas, top dimensions, countries and languages
POST/v2/property/signals/analytics/graphsignals:readOne metric bucketed over time, optionally against the previous period
POST/v2/property/signals/analytics/livesignals:readThe same graph shape for the last 5 minutes
POST/v2/property/signals/sessionssignals:readPaginated session documents
Terminal window
curl https://api.snipform.io/v2/property/signals/analytics/metrics \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"period": "last_28",
"clauses": [
{ "id": "channel", "op": "equals", "value": ["paid_search", "paid_social"] },
{ "id": "country_code", "op": "equals", "value": "ZA" }
]
}'
FieldTypeDefaultNotes
periodstringThe property’s default period, else last_7One of today, yesterday, last_7, last_28, month_to_date, year_to_date, last_12_months, custom. Anything else is a 422
date_fromYYYY-MM-DDRequired when period is custom. Start of day, UTC
date_toYYYY-MM-DDRequired when period is custom. End of day, UTC
clausesarray[]Filters. See Query language

The live endpoint takes no period; its window is fixed.

Every response echoes what it used under inputs, describes the window under meta.showing (for example Last 28 Days or Aug 1st, 2026 - Aug 21st, 2026), repeats the resolved filters under meta.filters, reports Elasticsearch time under meta.took_ms, and lists the valid choices under options so a UI can build its own pickers without hard-coding anything.

POST /v2/property/signals/analytics/metrics

Extra request fields: none in effect. filter and show_devices are accepted and echoed in inputs for forward compatibility but do not change the response today.

{
"inputs": { "period": "last_28", "date_from": null, "date_to": null, "clauses": [], "filter": null, "show_devices": false },
"analytics": {
"period_metrics": {
"summary": {
"sessions": { "current": 4210, "previous": 3877, "difference": 333, "percent": 0.0859, "percent_abs": 0.0859, "trend": "up", "trend_color": "success" },
"views": { "current": 9120, "previous": 8410, "difference": 710, "percent": 0.0844, "percent_abs": 0.0844, "trend": "up", "trend_color": "success" },
"views_session": { "current": 2.17, "previous": 2.17, "difference": 0, "percent": 0, "percent_abs": 0, "trend": "flat", "trend_color": "base" },
"duration": { "current": 74.2, "previous": 69.8, "difference": 4.4, "percent": 0.063, "percent_abs": 0.063, "trend": "up", "trend_color": "success" },
"scroll": { "current": 48.1, "previous": 51.0, "difference": -2.9, "percent": -0.0569, "percent_abs": 0.0569, "trend": "down", "trend_color": "danger" },
"bounce": { "current": 0.41, "previous": 0.44, "difference": -0.03, "percent": -0.0682, "percent_abs": 0.0682, "trend": "down", "trend_color": "success" },
"cost": { "current": 182000, "previous": 175500, "difference": 6500, "percent": 0.037, "percent_abs": 0.037, "trend": "up", "trend_color": "danger" },
"value": { "current": 641900, "previous": 512300, "difference": 129600, "percent": 0.253, "percent_abs": 0.253, "trend": "up", "trend_color": "success" }
},
"can_compare": true,
"metrics": [
{ "key": "sessions", "label": "Sessions", "format": "short", "is_reverse": false },
{ "key": "bounce", "label": "Bounce Rate", "format": "percent", "is_reverse": true }
],
"query_ms": 41.2
},
"period_top_dimensions": {
"count": 4210,
"dimensions": {
"source": { "id": "source", "field": "source_name", "type": "keyword", "label": "Source", "data": [ { "id": "Google", "label": "Google", "value": 1810, "order": 1, "percent": 0.43 } ] },
"channel": { "id": "channel", "field": "channel_category", "type": "keyword", "label": "Channel", "data": [ ] },
"referrer_domain": { "data": [ ] },
"entry_path_full": { "data": [ ] },
"exit_path_full": { "data": [ ] },
"country": { "data": [ ] },
"region": { "data": [ ] },
"city": { "data": [ ] },
"timezone": { "data": [ ] },
"browser": { "data": [ ] },
"os": { "data": [ ] },
"device": { "data": [ ] },
"language": { "data": [ ] }
},
"deviceSizes": { "mobile": 2600, "desktop": 1500, "tablet": 110 },
"query_ms": 58.7
},
"period_country_lang": {
"countries": { "ZA": 2410, "US": 690, "GB": 310 },
"languages": { "EN": 3900, "AF": 210 },
"query_ms": 22.0
}
},
"meta": { "showing": "Last 28 Days", "filters": [], "took_ms": 121.9 },
"options": { "periods": [ { "id": "today", "name": "Today" } ] }
}

period_metrics.summary - eight metrics, each compared with the equal-length window immediately before the selected one:

KeyUnitis_reverse
sessionscountno
viewscountno
views_sessionratio, 2 decimalsno
bouncefraction 0-1yes (down is good)
durationaverage secondsno
scrollaverage max scroll depth, 0-100no
costUSD cents, summedyes
valueUSD cents, summedno

percent is the relative change as a fraction (0.0859 = +8.59%). trend is up, down or flat; trend_color already accounts for is_reverse, so a falling bounce rate is success. When the previous window has no sessions, previous, difference, percent and trend are null and can_compare is false. The metrics array is the display catalog (label, number format, reverse flag) for anything that renders these cards.

period_top_dimensions - count is the number of sessions in the window after filters. dimensions holds the top 10 values for thirteen fields, keyed by their catalog id. Each entry carries the catalog row (id, field, type, label, description, category) plus data: an ordered list of { id, label, value, order, percent } where percent is the share of that field’s top-10 total. Some dimensions are enriched: sources and referrer domains gain favicon URLs, channels gain display labels, and geo entries gain country codes. deviceSizes is the raw device-class histogram.

period_country_lang - up to 300 countries as { ISO-2: count }, and languages collapsed to their primary subtag (en-ZA and en-US both count toward EN), top 12.

POST /v2/property/signals/analytics/graph

FieldTypeDefaultNotes
metricstringsessionssessions, views, views_session, bounce, duration, scroll
intervalstringAuto from the windowhour, day, week, month, year. Must be sensible for the span; options.intervals lists what is
compareboolfalseAlso return the same buckets for the preceding window
Terminal window
curl https://api.snipform.io/v2/property/signals/analytics/graph \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "period": "last_7", "metric": "sessions", "interval": "day", "compare": true }'
{
"inputs": { "period": "last_7", "date_from": null, "date_to": null, "metric": "sessions", "interval": "day", "compare": true, "clauses": [] },
"graph": {
"metric": "sessions",
"metricLabel": "Sessions",
"series": [
{ "label": "Aug 15 '26", "value": 612 },
{ "label": "Aug 16 '26", "value": 587 },
{ "label": "Aug 21 '26", "value": 203 }
],
"previousSeries": [
{ "label": "Aug 15 '26", "value": 540 },
{ "label": "Aug 16 '26", "value": 560 }
],
"projectedSeries": [
{ "label": "Aug 20 '26", "value": 598 },
{ "label": "Aug 21 '26", "value": 609 }
],
"query_ms": 33.4
},
"meta": { "showing": "Last 7 Days", "filters": [], "took_ms": 33.4 },
"options": {
"metrics": [ { "id": "sessions", "label": "Sessions" }, { "id": "views", "label": "Page views" } ],
"periods": [ { "id": "today", "name": "Today" } ],
"intervals": [ { "id": "day", "name": "Days", "seconds": 86400, "buckets": 7 } ]
}
}
  • series is one point per bucket across the selected window. previousSeries is the preceding window of equal length and is empty unless compare is true. It reuses the current labels so the two lines align point for point.
  • projectedSeries exists only for running totals (sessions, views) when the last bucket is still in progress: two points, the last complete bucket and the current bucket extrapolated to full size. Draw it dotted. It is empty for rates and averages, and below 2% of a bucket elapsed.
  • Values per metric: sessions count, views sum, views_session ratio (3 decimals), bounce percent 0-100 (3 decimals), duration average seconds (rounded), scroll average 0-100 (rounded).

When interval is omitted the server picks one from the window length. The candidates it will accept appear in options.intervals.

Window lengthDefaultAccepted
under 2 dayshourhour, day
2 to 7 daysdayday
7 to 30 daysdayday, week
30 to 180 daysweekday, week, month
180 to 365 daysmonthweek, month
1 to 10 yearsmonthweek, month, year

Bucket labels: 13h for hours (20-13h when the window spans days), Aug 21 '26 for days, Week (15 Aug - 21 Aug) for weeks, Aug '26 for months, 2026 for years.

POST /v2/property/signals/analytics/live

The window is always the last 5 minutes. Accepted fields: metric (same list as graph) and clauses. Returns the graph shape above with meta.showing set to Live - last 5 minutes; buckets are chosen automatically. Poll this rather than computing “now” from the metrics endpoint.

Terminal window
curl https://api.snipform.io/v2/property/signals/analytics/live \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "metric": "sessions" }'

POST /v2/property/signals/sessions

FieldTypeDefaultNotes
limitint20Sessions per page
pageint1Query-string parameter, as in ?page=3

Plus the shared period, date_from, date_to and clauses.

Terminal window
curl "https://api.snipform.io/v2/property/signals/sessions?page=2" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"period": "yesterday",
"limit": 50,
"clauses": [ { "id": "event_name", "op": "equals", "value": "purchase" } ]
}'

The paginator is spread at the root of data, with inputs, meta and options alongside:

{
"data": [
{
"id": "pD4kq9eBzR2x",
"type": "user",
"entry_ts": 1755720012,
"last_ts": 1755720498,
"entry_date": "2026-08-20 19:20:12",
"time_on_site": 486,
"views": 4,
"bounced": false,
"avg_max_scroll": 71.5,
"entry_url": "https://acme.com/pricing?utm_source=google&utm_medium=cpc",
"entry_path": "/pricing",
"entry_title": "Pricing - Acme",
"exit_path": "/thanks",
"referrer_domain": "google.com",
"channel_category": "paid_search",
"channel_name": "Google",
"channel_method": "utm",
"channel": { "category": "paid_search", "label": "Paid Search" },
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "brand-za",
"source_name": "Google",
"country_name": "South Africa",
"request_country": "ZA",
"request_device": "mobile",
"request_browser_name": "Chrome",
"request_platform": "Android",
"request_lang": "en-ZA",
"request_timezone": "Africa/Johannesburg",
"page_tags": [ { "url": "https://acme.com/pricing", "path": "/pricing", "count": 2 } ],
"event_tags": [ { "name": "purchase", "value": "4999", "value_float": 4999 } ],
"tags": [ { "key": "gclid", "value": "Cj0KCQjw..." } ],
"form_tags": null,
"contact_id": null,
"signal_value_amount": 4999,
"signal_value_currency": "ZAR",
"signal_value_amount_usd": 275,
"signal_cost_amount": 120,
"signal_cost_source": "google_ads",
"re_entry_count": 0
}
],
"current_page": 2,
"per_page": 50,
"total": 1377,
"last_page": 28,
"from": 51,
"to": 100,
"next_page_url": "https://api.snipform.io/v2/property/signals/sessions?page=3",
"prev_page_url": "https://api.snipform.io/v2/property/signals/sessions?page=1",
"query_ms": 19.6,
"inputs": { "period": "yesterday", "date_from": null, "date_to": null, "clauses": [ ], "limit": 50 },
"meta": { "showing": "Yesterday", "filters": [ ], "took_ms": 19.6 },
"options": { "periods": [ ] }
}

Each row is the stored session document. Field names are the underlying storage names, which differ from the query ids in places (country_name is queried as country, request_device as device). The field catalog lists both side by side. The id is the value to pass as session_id to session actions and contact identify. Only type: "user" sessions are returned.

Not every key is present on every session: money fields appear only once a value or cost has been recorded, contact_id only after identification, form_tags only after a form interaction.