API Reference

Query API

Three read-only resources answer the three questions the dashboards answer. All share the same request shape and the same response shape.

ResourceEndpointAnswers
EventsGET /api/v1/analytics/eventsHow many times did X happen?
SessionsGET /api/v1/analytics/sessionsHow engaged are visitors?
ContentGET /api/v1/analytics/contentWhich content performs best, or drives conversions?

How a Query Is Built#


Every request combines three kinds of parameter. Getting these three straight is most of what you need to know, because all three resources work the same way.

KindWhat it doesExamples
MetricThe number you want measured — a count, a rate or an average. Always numeric, and the only thing that appears in totals.totalEvents, uniqueVisitors, engagementRate
DimensionHow to group the metric. Always a label, never summed. Adds one row per distinct value.day, device, browser
FilterNarrows which rows are counted at all, before any grouping. Never appears as a column.siteId, eventType, conversionName

Think of it as a pivot table: dimensions are the row labels, metrics are the numeric columns. Request no dimensions and everything collapses into a single row — the total for the whole window, which is what a KPI tile needs. Request one or more and you get a row per combination, which is what a chart or table needs.

Response Shape#


Responses come wrapped in the standard dotCMS envelope. The query result is under entity:

{
  "entity": {
    "params":     { "from": "2026-08-06", "to": "2026-08-12", "project": "demo", … },
    "columns":    [ { "name": "day", "type": "DIMENSION" },
                    { "name": "totalEvents", "type": "METRIC" } ],
    "rows":       [ { "day": "2026-08-06", "totalEvents": 1240 } ],
    "totals":     { "totalEvents": 9423 },
    "pagination": { "page": 1, "pageSize": 20, "totalPages": 3, "totalItems": 60 }
  },
  "errors": [], "messages": [], "pagination": null, "permissions": []
}
FieldNotes
paramsEchoes the request as it actually ran, with range resolved to absolute from/to. Parameters you did not send are omitted rather than returned as null.
columnsThe schema of each row, in order, each tagged DIMENSION or METRIC. Build your table headers and chart axes from this instead of hardcoding field names.
rowsAlways an array. One entry for a scalar query, one per combination for a grouped one, and an empty array — not an error — when there is no data in the window.
totalsPresent only when you requested at least one dimension. For a scalar query rows[0] already is the total. Rates and averages here are recomputed from the underlying sums across the whole window, never averaged from the per-row rates.
paginationPresent only when you send page or pageSize.

Common Parameters#


ParameterRequiredNotes
siteIdNoRestricts to one site. Falls back to the current site.
from / toNo*Absolute window, YYYY-MM-DD. to may not be before from.
rangeNo*Relative window, last_N_days. Resolves to the N complete days ending yesterday — today is always excluded. Capped at 90 days.
metricsNoComma-separated. Each resource has its own default set.
dimensionsNoComma-separated. Omit for a whole-window scalar.
orderBy / orderDirNoSort by any requested metric or dimension; asc or desc.
page / pageSizeNoSending either one activates pagination.
projectNoInjected automatically from the instance configuration when omitted.

*Send exactly one of from+to or range. Sending both, or half of one, returns 400.

Events#


Answers "how many times did this happen." The three metrics fall into two groups, and a metric's group decides which dimensions you may group it by:

Metric groupMetricsDimensions you can pair with it
Event totalstotalEvents, uniqueVisitorsday, month, eventType
Pageview breakdownpageviewsday, month, device, browser

Grouping by device or browser is only possible with pageviews; grouping by event type is only possible with the other two, since a page view is already a single event type.

MetricMeaningGroup
totalEvents (default)Count of all matching events.Event totals
uniqueVisitorsDistinct visitor count.Event totals
pageviewsCount of page-view events.Pageview breakdown
DimensionMeaningValid with
dayCalendar-day bucket.any metric
monthCalendar-month bucket.any metric — mutually exclusive with day
eventTypeGroup by event type.totalEvents, uniqueVisitors
deviceDesktop / Mobile / Tablet / Other.pageviews only
browserChrome / Safari / Firefox / Edge / Other.pageviews only

Events is the only resource that accepts more than one dimension per request. You can also request several metrics at once, as long as every dimension you ask for is valid for all of them: metrics=totalEvents,uniqueVisitors&dimensions=day works, but metrics=totalEvents,pageviews&dimensions=device returns 400, because device pairs only with pageviews. The eventType filter is rejected outright alongside pageviews, which is already a single event type.

Example: Daily Trend#

curl -s "$BASE_URL/api/v1/analytics/events\
?range=last_14_days&metrics=totalEvents,uniqueVisitors&dimensions=day"
"entity": {
  "columns": [ { "name": "day", "type": "DIMENSION" },
               { "name": "totalEvents", "type": "METRIC" },
               { "name": "uniqueVisitors", "type": "METRIC" } ],
  "rows": [ { "day": "2026-07-25", "totalEvents": 3210, "uniqueVisitors": 1180 },
            { "day": "2026-07-26", "totalEvents": 3450, "uniqueVisitors": 1225 } ],
  "totals": { "totalEvents": 48213, "uniqueVisitors": 16720 }
}

Sessions#


Answers "how engaged are visitors." Eight metrics in two tiers — and the dimension you pick decides how many of them you can ask for:

If you group by…Metrics available
nothing — a whole-window totalall 8 (Basic + Extended)
dayall 8 (Basic + Extended)
device, browser or languagethe 4 Basic only

The whole-window total and the day-by-day breakdown are the two detailed views; the other three are lighter and do not track the Extended metrics at all.

MetricMeaningTier
totalSessionsTotal session count.Basic
engagedSessionsSessions that meet the engagement rule.Basic
engagementRateengagedSessions / totalSessions × 100Basic
avgEngagedSessionTimeSecondsAverage duration of engaged sessions.Basic
engagedConversionSessionsEngaged sessions that also converted.Extended
conversionRateengagedConversionSessions / totalSessions × 100 — the site conversion rate.Extended
avgInteractionsPerEngagedSessionAverage event count per engaged session.Extended
avgSessionTimeSecondsAverage duration across all sessions.Extended
DimensionMeaningMetrics available
(none)Whole-window scalar.all eight
dayCalendar-day bucket.all eight
deviceDevice category.Basic only
browserBrowser family.Basic only
languageContent locale — blank shows as "n/a".Basic only

Sessions accepts at most one dimension, and there is no month bucket. Crossing the tiers — an Extended metric with device, browser or language — returns 400.

Omitting metrics follows the same split: all eight for a scalar query or grouped by day, the four Basic ones otherwise. Default sort order varies too — day ascends chronologically, device and browser ascend alphabetically, and language sorts by engagedSessions descending.

Example: Engagement by Device#

curl -s "$BASE_URL/api/v1/analytics/sessions?range=last_30_days&dimensions=device"
"rows": [
  { "device": "Desktop", "totalSessions": 5210, "engagedSessions": 3312,
    "engagementRate": 63.57, "avgEngagedSessionTimeSeconds": 248.1 },
  { "device": "Mobile",  "totalSessions": 3450, "engagedSessions": 1890,
    "engagementRate": 54.78, "avgEngagedSessionTimeSeconds": 198.4 }
]

Content#


Answers "which content performs best." One endpoint, two modes — and which one you get is inferred from the parameters you send rather than chosen explicitly.

ModeRanks content byMetricsDimensions
Top contentRaw event volumetotalEvents onlyidentifier, title
Attribution (default)How often it preceded a conversiontotalEvents, attributionCount, attributionRateidentifier, title, eventType

Attribution is the richer view and the default, so you have to narrow deliberately to metrics=totalEvents — with none of the other triggers below — to get the simpler ranking instead.

If you…You get
Omit metrics entirelyAttribution (the default)
Send metrics=totalEvents and nothing belowTop content
Request attributionCount or attributionRateAttribution
Request the eventType dimensionAttribution
Send conversionNameAttribution

attributionCount is the number of conversions the content preceded, and attributionRate is attributionCount / totalEvents × 100. Watch out for totalEvents: it means raw event volume in top-content mode but impressions that preceded a conversion in attribution mode — the same field name, two different numbers.

Omit dimensions and you get identifier,title in top-content mode, identifier,title,eventType in attribution mode.

conversionName restricts results to content that preceded that specific conversion, and selects attribution mode on its own. A request with no page/pageSize returns every matching row, which on a large catalogue can be hundreds — paginate.

Example: Conversion Drivers#

curl -s "$BASE_URL/api/v1/analytics/content\
?range=last_30_days&conversionName=form_submit&orderBy=attributionRate&orderDir=desc&pageSize=20"
"rows": [
  { "identifier": "abc-123", "title": "Spring Sale Landing Page", "eventType": "pageview",
    "totalEvents": 4210, "attributionCount": 512, "attributionRate": 12.16 },
  { "identifier": "def-456", "title": "Product Launch Blog Post", "eventType": "pageview",
    "totalEvents": 3185, "attributionCount": 201, "attributionRate": 6.31 }
]

Errors#


StatusWhen
400Malformed dates, range over 90 days, unsupported metric or dimension, incompatible metric + dimension pair.
401Not authenticated as a dotCMS back-end user.
403SITE_ACCESS_DENIED — the user lacks READ permission on the requested site.
502dotCMS reached the analytics service but could not authenticate to it — a service configuration problem, not a session problem.