Skip to content
Clics privacy-friendly cookieless web analytics documentation
Esc
navigateopen⌘Jpreview
On this page

Query Clics analytics metrics with the TypeScript SDK

Copy page

Query analytics stats

Query Clics analytics with the TypeScript SDK: metrics, date ranges, dimensions, filters, and pagination.

Runs an analytics query against a project: the same engine that powers the Clics dashboard. This is the primary way to read traffic and conversion data programmatically.

Use it to verify tracking is working, build internal reports, or sync KPIs to other tools.

Parameters

Field Required Description
projectId Yes Project to query
metrics Yes At least one metric (see below)
dateRange Yes Preset or [startISO, endISO] custom range
domain No Filter to one domain. Omit for all production domains. Use "localhost" for dev traffic
dimensions No Group results (country, page, UTM, time buckets, etc.)
filters No Narrow results: [operator, dimension, values[]] tuples
orderBy No Sort: [field, "asc" | "desc"] tuples
include.previousPeriod No Include comparison to the prior period
include.totalRows No Return total row count in meta
pagination.limit No Max rows (1–1000)
pagination.offset No Skip rows for paging
timezone No IANA timezone for calendar boundaries and time dimensions. Defaults to UTC

Metrics

Query shape Supported metrics
KPI or time series visitors, visits, pageviews, bounce_rate, visit_duration, views_per_visit
Breakdown visitors, pageviews, conversion_rate, events
Real-time window visitors only

Use only the metrics supported by the shape you are querying. conversion_rate is a percentage; events is the number of events in an event or breakdown result.

Date range presets

last24h, last7days, last30days, last3months, last12months, monthToDate, quarterToDate, yearToDate, allTime

Dimensions

Category Dimensions
Content and events event:page, event:hostname, event:name, event:outbound_url
Visitor visit:country, visit:device, visit:browser, visit:os, visit:referrer, referrer:ai_provider
Campaign visit:utm_source, visit:utm_medium, visit:utm_campaign, visit:utm_term, visit:utm_content
Time time, time:hour, time:day

Use one time dimension (time, time:hour, or time:day) or up to two non-time breakdown dimensions. Do not combine time and non-time dimensions in one query.

Filter operators

is, is_not, contains, contains_not

The filterable fields are event:page, event:hostname, event:goal, event:name, event:outbound_url, visit:country, visit:device, visit:browser, visit:os, visit:referrer, referrer:ai_provider, and every visit:utm_* dimension. A goal filter uses the goal’s tracked value, for example signup_completed for an event goal.

Signature

clics.stats.queryStats(request, options?)

Examples

All examples use this server-side client. Keep the API key out of browser code.

import { Clics } from "@clicsdev/sdk";
import type { StatsQueryRequest } from "@clicsdev/sdk/models";

const clics = new Clics({ apiKey: process.env.CLICS_API_KEY! });
const projectId = "proj_xxx";
type FilterExpression = NonNullable<StatsQueryRequest["filters"]>[number];

KPI overview

Use no dimensions for one aggregate result for the whole selected scope.

const overview = await clics.stats.queryStats({
  projectId,
  metrics: [
    "visitors",
    "visits",
    "pageviews",
    "bounce_rate",
    "visit_duration",
    "views_per_visit",
  ],
  dateRange: "last30days",
});

console.log(overview.results[0]?.metricValues);

Every preset date range

The SDK accepts all nine Dashboard presets. This loop shows every valid preset; normally, choose just one for your report.

const presets = [
  "last24h",
  "last7days",
  "last30days",
  "last3months",
  "last12months",
  "monthToDate",
  "quarterToDate",
  "yearToDate",
  "allTime",
] as const;

const resultsByPeriod = await Promise.all(
  presets.map((dateRange) =>
    clics.stats.queryStats({
      projectId,
      metrics: ["visitors", "pageviews"],
      dateRange,
    }),
  ),
);

Custom calendar range and exact timestamps

Date-only strings query inclusive calendar days. Use full ISO8601 timestamps when you need an exact instant range; the timezone offset is respected.

const julyCalendar = await clics.stats.queryStats({
  projectId,
  metrics: ["visitors", "pageviews"],
  dateRange: ["2026-07-01", "2026-07-31"],
  timezone: "Europe/London",
});

const launchWindow = await clics.stats.queryStats({
  projectId,
  metrics: ["visitors", "visits"],
  dateRange: [
    "2026-07-31T09:00:00+01:00",
    "2026-07-31T17:00:00+01:00",
  ],
});

Domain scope: production, normalized hostnames, and localhost

Omit domain for all production domains. Clics normalizes schemes, paths, and ports. Use exactly "localhost" for development traffic.

const oneDomain = await clics.stats.queryStats({
  projectId,
  domain: "https://www.example.com/pricing?source=docs",
  metrics: ["visitors", "pageviews"],
  dateRange: "last7days",
});

const localDevelopment = await clics.stats.queryStats({
  projectId,
  domain: "localhost",
  metrics: ["visitors"],
  dateRange: "last7days",
});

Real-time current visitors

A custom timestamp range of five minutes or less, with no dimensions and only the visitors metric, returns current visitors.

const now = new Date();
const fiveMinutesAgo = new Date(now.getTime() - 5 * 60 * 1000);

const currentVisitors = await clics.stats.queryStats({
  projectId,
  metrics: ["visitors"],
  dateRange: [fiveMinutesAgo.toISOString(), now.toISOString()],
});

console.log(currentVisitors.results[0]?.metricValues.visitors);

Time series: every time dimension

Use a time dimension with KPI/time-series metrics. Do not combine it with a non-time breakdown dimension.

const timeDimensions = ["time", "time:hour", "time:day"] as const;

const timeSeries = await Promise.all(
  timeDimensions.map((dimension) =>
    clics.stats.queryStats({
      projectId,
      metrics: ["visitors", "pageviews", "bounce_rate"],
      dateRange: "last30days",
      timezone: "UTC",
      dimensions: [dimension],
      orderBy: [[dimension, "asc"]],
    }),
  ),
);

Single breakdowns: every supported dimension

For regular traffic dimensions, use the four breakdown metrics. Custom-event and outbound-link dimensions use the event metrics shown separately.

const trafficBreakdowns = [
  "event:page",
  "event:hostname",
  "visit:country",
  "visit:device",
  "visit:browser",
  "visit:os",
  "visit:referrer",
  "referrer:ai_provider",
  "visit:utm_source",
  "visit:utm_medium",
  "visit:utm_campaign",
  "visit:utm_term",
  "visit:utm_content",
] as const;

const allTrafficBreakdowns = await Promise.all(
  trafficBreakdowns.map((dimension) =>
    clics.stats.queryStats({
      projectId,
      metrics: ["visitors", "pageviews", "conversion_rate", "events"],
      dateRange: "last30days",
      dimensions: [dimension],
      orderBy: [["visitors", "desc"]],
      pagination: { limit: 25, offset: 0 },
    }),
  ),
);

const customEvents = await clics.stats.queryStats({
  projectId,
  metrics: ["visitors", "events", "conversion_rate"],
  dateRange: "last30days",
  dimensions: ["event:name"],
  orderBy: [["events", "desc"]],
});

const outboundLinks = await clics.stats.queryStats({
  projectId,
  metrics: ["visitors", "events", "conversion_rate"],
  dateRange: "last30days",
  dimensions: ["event:outbound_url"],
  orderBy: [["events", "desc"]],
});

Two-dimensional breakdown

Use at most two non-time dimensions to compare segments.

const countryByDevice = await clics.stats.queryStats({
  projectId,
  metrics: ["visitors", "pageviews", "conversion_rate", "events"],
  dateRange: "last30days",
  dimensions: ["visit:country", "visit:device"],
  orderBy: [
    ["visitors", "desc"],
    ["visit:country", "asc"],
  ],
  pagination: { limit: 100, offset: 0 },
});

Every filter field and operator

Each entry below is a complete, valid single-filter request. Run one at a time, or combine filters that target different fields (for example country + device + page). contains and contains_not are valid operators; is_not and contains_not exclude matching values.

const filterRequests: FilterExpression[] = [
  ["is", "event:page", ["/pricing"]],
  ["is_not", "event:hostname", ["staging.example.com"]],
  ["contains", "event:name", ["purchase"]],
  ["contains_not", "event:outbound_url", ["https://untrusted.example"]],
  ["is", "event:goal", ["signup_completed"]],
  ["is", "visit:country", ["FR", "MA"]],
  ["is", "visit:device", ["Desktop"]],
  ["is", "visit:browser", ["Chrome"]],
  ["is", "visit:os", ["Windows"]],
  ["is", "visit:referrer", ["google.com"]],
  ["is", "referrer:ai_provider", ["chatgpt", "claude"]],
  ["is", "visit:utm_source", ["newsletter"]],
  ["is", "visit:utm_medium", ["email"]],
  ["is", "visit:utm_campaign", ["summer_launch"]],
  ["is", "visit:utm_term", ["privacy analytics"]],
  ["is", "visit:utm_content", ["hero_cta"]],
];

for (const filter of filterRequests) {
  await clics.stats.queryStats({
    projectId,
    metrics: ["visitors", "pageviews"],
    dateRange: "last30days",
    filters: [filter],
  });
}

Here is a practical compatible combination:

const frenchPricingTraffic = await clics.stats.queryStats({
  projectId,
  metrics: ["visitors", "pageviews"],
  dateRange: "last30days",
  filters: [
    ["is", "event:page", ["/pricing"]],
    ["is", "visit:country", ["FR"]],
    ["is_not", "visit:device", ["Tablet"]],
    ["contains", "visit:utm_source", ["newsletter"]],
  ],
});

AI-provider traffic

referrer:ai_provider groups known referrers under provider names, and can also be used as a filter.

const aiTraffic = await clics.stats.queryStats({
  projectId,
  metrics: ["visitors", "pageviews", "conversion_rate"],
  dateRange: "last3months",
  dimensions: ["referrer:ai_provider"],
  filters: [["is", "referrer:ai_provider", ["chatgpt", "perplexity"]]],
  orderBy: [["visitors", "desc"]],
});

Previous-period comparison and total rows

previousPeriod is for KPI queries with no dimensions. totalRows is useful for paged time-series or breakdown queries.

const comparedOverview = await clics.stats.queryStats({
  projectId,
  metrics: ["visitors", "pageviews", "bounce_rate"],
  dateRange: "last30days",
  include: { previousPeriod: true },
});

console.log(comparedOverview.comparison?.previousMetricValues.visitors);
console.log(comparedOverview.comparison?.changePercent.visitors);

const firstCountryPage = await clics.stats.queryStats({
  projectId,
  metrics: ["visitors", "pageviews"],
  dateRange: "last30days",
  dimensions: ["visit:country"],
  include: { totalRows: true },
  pagination: { limit: 50, offset: 0 },
});

console.log(firstCountryPage.meta.total_rows);

What it returns

type StatsQueryResponse = {
  results: Array<{
    // Positional values, aligned with the requested metrics and dimensions.
    metrics: Array<number | null>;
    dimensions: Array<string | number>;
    // Prefer these named maps when reading a result in application code.
    metricValues: Record<string, number | null>;
    dimensionValues: Record<string, string | number>;
  }>;
  meta: Record<string, unknown>;
  comparison?: {
    previousMetricValues: Record<string, number | null>;
    changePercent: Record<string, number>;
  };
  query: StatsQueryRequest; // normalized echo of your request
};

metrics in each result row align with the metrics array in your request. dimensions align with dimensions when provided. For most code, use the named maps instead: result.results[0]?.metricValues.visitors and result.results[0]?.dimensionValues["visit:country"] are clearer and do not depend on array order.

comparison is present only when include.previousPeriod: true is used on a KPI query without dimensions. previousMetricValues contains the previous range; changePercent contains the percentage change for each requested metric.

Field When present Meaning
results Always One aggregate, time bucket, or breakdown row per query result
results[].metricValues Always Named values keyed by the metrics requested, such as visitors or pageviews
results[].dimensionValues With dimensions Named values keyed by each requested dimension
meta.timezone Always IANA timezone used for calendar boundaries and time dimensions
meta.total_rows With include.totalRows: true Total matching breakdown rows before pagination.limit and pagination.offset
comparison With include.previousPeriod: true and no dimensions Equivalent metrics for the preceding period plus percentage change
query Always Normalized echo of the effective SDK request, useful for logging and debugging

Notes

  • Requires a paid plan: returns 403 on free tiers
  • Set domain: "localhost" to query dev traffic (project must have allowLocalhost: true)
  • Use include.previousPeriod: true to compare week-over-week or month-over-month

API reference

See Query analytics stats for request and response schemas, status codes, and error types.

Was this page helpful?