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:
| Form | Example | Meaning |
|---|---|---|
last | last=7d, last=24h, last=30m | The 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 dates | start=2026-10-01&end=2026-10-07 | Whole 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 instants | start=2026-10-01T00:00:00Z&end=2026-10-08T00:00:00Z | Exactly that span: start included, end excluded. ISO 8601 with a Z or an offset (+02:00); start must be before end. |
all=true | all=true | All 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:
| Window | Bucket |
|---|---|
| up to 2 hours | minute |
| up to 2 days | hour |
| up to 92 days | day |
| up to 730 days | week |
| longer | month |
all=true | month (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:
| Parameter | Comparison window |
|---|---|
compare=previous | The span of the same length just before the window. For a date window, the same number of whole days before it. |
compare=year | The same dates or times one year earlier. |
compare_start and compare_end | A 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):
{
"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
op | Values | Matches |
|---|---|---|
eq / neq | strings, numbers or true/false | Exactly one of the values (case-sensitive) / none of them. |
contains / not_contains | strings | Contains one of the values, ignoring case / contains none of them. |
starts_with, ends_with | strings | Starts or ends with one of the values, ignoring case. |
regex / not_regex | strings | Matches 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_set | none ([] 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, lte | exactly one number | Greater 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.
| Dimension | What it is |
|---|---|
entry_page, exit_page | Path of the session's first and last pageview. |
referrer, referrer_domain, channel | Where the visit came from, and its channel (such as Direct, Referral, Organic Search). |
utm_source, utm_medium, utm_campaign, utm_term, utm_content | UTM tags of the landing URL. |
affiliate | The affiliate of the visit. |
country, region, city, continent, timezone | Location 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, language | Device and browser. screen is WIDTHxHEIGHT. |
asn | Number of the visitor's network (autonomous system). Numeric. |
tag | The tag field of the session's events (free text up to 64 characters), first one set. |
visitor_id, user_id, identified | The visitor id, the identified user id, and whether the session belongs to an identified user (true/false). See below. |
geo_point, geo_h3 | A 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:
| Dimension | What it is |
|---|---|
pathname | The page path. |
page_title | The page title. |
type | The event type: pageview, event (custom events), outbound, click, form, input, copy, error, vital. |
event_name | The name of a custom event or an error. |
error_fingerprint | The 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, querystring | The 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:
| Dimensions | Without sessions:read |
|---|---|
user_id, visitor_id, identified | 400 Filtering by user_id, visitor_id or identified requires the sessions:read permission |
querystring, hostname, prop:ref, prop:message, prop:stack, prop:file | 400 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"]}]'{
"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
404Segment not found, as for an id that does not exist. - The
sessions:readrule 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.