Count a project's or one pixel's website events over a window of up to 24 hours, and break the counts down by up to two fields. Use it to see how much traffic a day carried and where it came from, before you query the events themselves.
Counts only, and not billed. The response carries counts and category values, never identifiers: no event IDs, browser cookie IDs, IP addresses, URLs, hashed emails, or person or company fields. The route bills nothing, whatever it returns.
Three measures, counted over the whole window in totals and for each group:
events: the number of events.visitors: the number of distinct browsers, by browser cookie. Events without a cookie are not counted here.resolved: the number of events resolved to a person.
Top groups. groups holds the top limit groups, at most 50, largest first by order_by. Groups with the same count come in ascending order of their values. totals always covers every matching event, not only the groups returned. Without group_by, groups is empty. Each group carries its field values under key, next to its measures, for example {"key": {"ip_country": "NL"}, "events": 1002, "visitors": 14, "resolved": 0}. So group_by=resolved gives groups such as {"key": {"resolved": true}, "events": 12, "visitors": 3, "resolved": 12}. A value the events did not carry, such as an ip_country that could not be placed, is null.
Browser ranking. group_by=browser ranks individual browsers by activity. Each browser is returned as an integer rank in the ordered result, starting at 1, never as its cookie ID. Events without a browser cookie form one group whose browser is null, and it takes no rank. A rank means nothing outside its response: rank 1 in two responses need not be the same browser.
Limits. The window is at most 24 hours and starts no more than 30 days ago. One request can also read only so many stored events: a window too large for the project or pixel returns 422 with code scan_too_large, and its message gives both numbers, how much the request would read (stored rows, files or bytes) and the limit. Narrow to one pixel, or shorten the window.
Freshness. Counts cover events the event store has already written, which it does within an hour of their arrival. A window that includes the most recent hour can come back short for that hour. Ask again later for a complete count.
Errors carry a human-readable error and a machine-readable code. Branch on code, not on the message:
invalid_request(400): a parameter is missing or invalid. The message names it.upstream_rejected(the event store's own4xx, usually400): the event store refused the query, for example afilterwith an unsupported operator. With408, the query ran too long: narrow the window, the pixel or the filter.not_found(404): the project or pixel was not found in your organization, or the pixel is not in the named project.method_not_allowed(405): the route takesGETonly.pixel_inactive(422): the pixel is inactive.scan_too_large(422): the window holds more stored events than one request can read. See Limits.internal_error(500): access could not be checked. Retry.upstream_error(502, or the event store's own5xx): the event store failed or is overloaded. Retry.unavailable(503): counts are temporarily unavailable. Retry shortly.upstream_timeout(504): the event store did not answer in time. Retry, or narrow the window.
A missing or invalid API key (401) and rate limiting (429) are answered before the route runs, so those responses carry no code.
Example. The top three countries by events on one day:
GET /api/v3/events/web_event/counts?project_id=3f2b8c1e-5d4a-4e7b-9a6c-1b2d3e4f5a6b&start_ms=1792022400000&end_ms=1792108800000&group_by=ip_country&limit=3| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||
401Missing or invalid API key. The body carries error only.
429Rate limited. Wait for the Retry-After header, in seconds, then retry. The body carries error only.
