PilotFoxDocs

Time windows, filters and comparisons

The parameters shared by the analytics endpoints: time windows, time zones, comparisons, filters and segments.

The analytics endpoints share one set of query parameters: a time window (with its time zone and bucket size), an optional comparison, filters and a saved segment. Each endpoint's reference page lists which of them it takes.

curl -G -H "Authorization: Bearer $PILOTFOX_API_KEY" \
  "https://pilotfox.app/api/v1/sites/SITE_ID/timeseries" \
  --data-urlencode 'start=2026-10-01' \
  --data-urlencode 'end=2026-10-07' \
  --data-urlencode 'tz=America/New_York' \
  --data-urlencode 'compare=year' \
  --data-urlencode 'metrics=visitors,pageviews' \
  --data-urlencode 'filters=[{"dim":"country","op":"eq","values":["US","CA"]}]'

-G --data-urlencode makes curl put each parameter in the query string, URL-encoded; filters hold JSON, which must be encoded.

Time window

Send exactly one of these forms; none, or two of them, is a 400:

FormExampleMeaning
lastlast=7d, last=24h, last=30mThe last n days (d), hours (h) or minutes (m) up to now. A positive whole number and a unit; at most 400d.
start and end as datesstart=2026-10-01&end=2026-10-07Whole days in the window's time zone, both included. If end is today or later, the window ends now. start may not be after end, nor in the future.
start and end as instantsstart=2026-10-01T00:00:00Z&end=2026-10-08T00:00:00ZExactly that span: start included, end excluded. ISO 8601 with a Z or an offset (+02:00); start must be before end.
all=trueall=trueAll time. Not available with a comparison; the bucket must be a day or longer.

start and end must both be dates or both be instants, within the years 1970 to 2299.

The resolved window comes back in meta.window: from and to as UTC instants (from is null for all=true), the time zone and the bucket.

Time zone

tz is an IANA time zone name, such as Europe/London or America/New_York, or UTC. Without it, the site's own time zone applies (set in the site settings). It decides where days begin for date windows, day and longer buckets, and the hour-of-week heatmap. Offsets (+02:00) and abbreviations (CET, EST) are refused. meta.window.tz holds the canonical name the server used, which can differ in spelling from the one you sent (for example an older alias).

Bucket

Time series group their points by bucket: minute, 5m, 15m, hour, day, week (ISO weeks, starting on Monday), month or year. Without it, PilotFox picks one from the window's length:

WindowBucket
up to 2 hoursminute
up to 2 dayshour
up to 92 daysday
up to 730 daysweek
longermonth
all=truemonth (day or longer if you choose)

A window may hold at most 1 500 buckets: last=30d&bucket=minute is a 400 (too many buckets: 43200 minute buckets (max 1500)). Each point's t is the start of its bucket, as an ISO 8601 instant.

Comparison

Ask for the same report over an earlier window, computed in the same request:

ParameterComparison window
compare=previousThe span of the same length just before the window. For a date window, the same number of whole days before it.
compare=yearThe same dates or times one year earlier.
compare_start and compare_endA window of your choice, in the same formats as start and end.

compare cannot be combined with compare_start/compare_end, nor any of them with all=true. The comparison uses the same time zone, bucket and filters.

These endpoints take a comparison: /overview, /timeseries, /goals/stats, /revenue/summary, /revenue/timeseries, /revenue/subscriptions/summary and the Search Console reports. The other endpoints compute no comparison.

The answer carries the comparison window in meta.compare, and the earlier values under previous: one object for a summary, and on each point of a time series (null where the comparison series has fewer points):

GET /overview?last=7d&compare=previous (abridged)
{
  "data": {
    "visitors": 8,
    "pageviews": 7,
    "previous": { "visitors": 0, "pageviews": 0 }
  },
  "meta": {
    "window": { "from": "2026-10-20T20:38:30.486Z", "to": "2026-10-27T20:38:30.486Z", "tz": "Europe/Berlin", "bucket": "day" },
    "compare": { "from": "2026-10-13T20:38:30.486Z", "to": "2026-10-20T20:38:30.486Z" }
  }
}

Filters

filters is a JSON array of conditions, URL-encoded into one query parameter. A condition is an object { "dim", "op", "values" }:

[
  { "dim": "pathname", "op": "starts_with", "values": ["/blog"] },
  { "dim": "country", "op": "eq", "values": ["US", "CA"] },
  { "dim": "prop:plan", "op": "neq", "values": ["free"] }
]
  • Conditions combine with AND. Every condition must hold.
  • Values combine with OR for the positive operators (country eq US or CA), and as none of for the negative ones (neq, not_contains, not_regex: matches none of the values).
  • Limits: at most 20 conditions, 50 values per condition, 500 characters per value and 300 per regular expression.

Operators

opValuesMatches
eq / neqstrings, numbers or true/falseExactly one of the values (case-sensitive) / none of them.
contains / not_containsstringsContains one of the values, ignoring case / contains none of them.
starts_with, ends_withstringsStarts or ends with one of the values, ignoring case.
regex / not_regexstringsMatches one of the regular expressions / none of them. RE2 syntax, case-sensitive (add (?i) to ignore case); lookarounds, backreferences, atomic groups and possessive quantifiers are not supported.
is_set / is_not_setnone ([] or omitted)The dimension has a value (a non-empty text; for asn, a known number; for prop:<key>, the event has the property) / it has none. Not for identified, which is true or false.
gt, gte, lt, lteexactly one numberGreater than, at least, less than, at most. For numeric dimensions only (asn, numeric prop:<key>).

A value of the wrong type is a 400 naming the problem, for example gt takes exactly one number. For asn, 0 means unknown: asn lt 2000 does not match visitors whose network is unknown.

Dimensions

Session dimensions describe a visit as a whole. Text attributes such as the referrer and the UTM tags are taken from the session's first event that has them; the channel is the first one other than Direct or Internal, if any.

DimensionWhat it is
entry_page, exit_pagePath of the session's first and last pageview.
referrer, referrer_domain, channelWhere the visit came from, and its channel (such as Direct, Referral, Organic Search).
utm_source, utm_medium, utm_campaign, utm_term, utm_contentUTM tags of the landing URL.
affiliateThe affiliate of the visit.
country, region, city, continent, timezoneLocation from the visitor's IP address. country is an ISO 3166-1 alpha-2 code (US).
browser, browser_version, os, os_version, device_type, screen, languageDevice and browser. screen is WIDTHxHEIGHT.
asnNumber of the visitor's network (autonomous system). Numeric.
tagThe tag field of the session's events (free text up to 64 characters), first one set.
visitor_id, user_id, identifiedThe visitor id, the identified user id, and whether the session belongs to an identified user (true/false). See below.
geo_point, geo_h3A location cell, as the map endpoints return it: "lat,lon" with up to two decimals, or an H3 cell index (15 hexadecimal digits). eq only.

Event dimensions describe single events:

DimensionWhat it is
pathnameThe page path.
page_titleThe page title.
typeThe event type: pageview, event (custom events), outbound, click, form, input, copy, error, vital.
event_nameThe name of a custom event or an error.
error_fingerprintThe fingerprint grouping an error, as /errors returns it.
prop:<key>A property of the event, such as prop:plan. The key has 1 to 64 characters. If every value of the condition is a number, the property is compared as a number; if every value is true or false, as a boolean; otherwise as text.
hostname, querystringThe page's host name and query string. See below.

How event conditions apply. A report about visits (visitors, sessions, bounce rate, breakdowns by a session dimension) counts the sessions that pass every session condition and have at least one event matching each event condition, or, for a negative operator, no matching event at all. All events of those sessions then count. A report about events (event lists, pages, event and error summaries) also keeps only the events that match.

An unknown dimension is a 400 (Unknown dimension: colour).

Dimensions that need sessions:read

Some dimensions single out a person, or hold values that only the endpoints for individual sessions return (a query string can carry a password-reset token or a payment id). Counting matches under starts_with or regex would spell such a value out one character at a time, so filtering by them needs the sessions:read permission on the site:

DimensionsWithout sessions:read
user_id, visitor_id, identified400 Filtering by user_id, visitor_id or identified requires the sessions:read permission
querystring, hostname, prop:ref, prop:message, prop:stack, prop:file400 Filtering by <dimension> requires the sessions:read permission

This applies to API keys without sessions:read, to share links without that scope and to everyone reading a public site, and also to the conditions of a segment they apply. prop:ref is the checkout reference that links payments to visits; prop:message, prop:stack and prop:file hold the text of errors (whatever the event name, since a condition names no event).

Breakdown dimensions

/breakdown groups by one session dimension, dim, and ranks the rows by metric (visitors by default, or sessions, pageviews, bounce_rate, avg_session_duration). Every session dimension above works except visitor_id, user_id, identified, geo_point and geo_h3, which are for filtering only.

curl -G -H "Authorization: Bearer $PILOTFOX_API_KEY" \
  "https://pilotfox.app/api/v1/sites/SITE_ID/breakdown" \
  --data-urlencode 'last=7d' \
  --data-urlencode 'dim=channel' \
  --data-urlencode 'filters=[{"dim":"pathname","op":"starts_with","values":["/pricing"]}]'
Response
{
  "data": [
    { "value": "Direct", "visitors": 1, "sessions": 1, "pageviews": 1, "bounce_rate": 100, "avg_session_duration": 0, "percentage": 100 }
  ],
  "meta": {
    "window": { "from": "2026-10-20T20:22:06.165Z", "to": "2026-10-27T20:22:06.165Z", "tz": "Europe/Berlin", "bucket": "day" },
    "totalCount": 1,
    "cache": "miss"
  }
}

Segments

A segment is a saved set of filter conditions (see /segments). Apply one with its id:

curl -G -H "Authorization: Bearer $PILOTFOX_API_KEY" \
  "https://pilotfox.app/api/v1/sites/SITE_ID/pages" \
  --data-urlencode 'last=30d' \
  --data-urlencode 'segment=a6d33c2c-6cf1-4850-96e4-f7ad3620d242' \
  --data-urlencode 'filters=[{"dim":"device_type","op":"eq","values":["mobile"]}]'
  • The segment's conditions come first, then those of filters; a condition repeated exactly is used once. Together they may hold at most 20 conditions (Too many filters after applying the segment (max 20)).
  • An API key can apply the organization's shared segments (those saved as public), whether saved for the whole organization or for this site. Private segments belong to the person who saved them; anyone else, a key included, gets 404 Segment not found, as for an id that does not exist.
  • The sessions:read rule applies to the segment's conditions too.

Every endpoint that takes filters also takes segment.

Other parameters

Endpoints add their own parameters, such as dim and metric on breakdowns, metrics on /timeseries (a comma list of the metrics to return), goal (a goal id, to count its conversions alongside) on breakdowns, pages, geography and journeys, and the paging parameters. Their reference pages list them.

The query string, all parameters together, must stay under 16 KB, or the server answers 431. That is far more than 20 conditions normally need.

On this page