Chart signal volume
GET /v2/signals/summary counts your signals into period buckets and returns
them as a series you can chart. It is built for a page that loads on every
view: it reads a pre-aggregated hourly rollup rather than your raw signals, so
a 90-day window answers in well under a second.
Use it for “signals per day”, “signals per team per week”, or any volume chart your product shows a customer. For a wide per-customer spreadsheet over arbitrary payload fields, use the batch export instead.
Prerequisites
- Have your API key ready. The
endpoint needs the
read:analyticsscope.
A first call
signalCount counts one per signal. quantityTotal adds up the quantity each
signal reported, so a single signal reporting a batch of 100 adds 100. They are
equal when every signal reported a single occurrence.
Buckets and time basis
Signals are bucketed on their effective timestamp in UTC — the same basis billing uses, so these counts reconcile with what you are invoiced for.
interval accepts hour, day, week (Monday start) and month. An hour is
the finest grain available, because the rollup behind these figures is itself
hourly. The first and last bucket’s published bounds are clipped to your
requested window, so a window starting at noon reports a first bucket that
starts at noon.
Only periods that recorded signals appear. A quiet day is absent rather than zero.
Grouping
groupBy=signalName cuts each period by the signal’s own name:
groupBy=dimension cuts each period by a field in your signal payload. Name
the field in dimensionKey:
Each point then carries a groupKey holding that signal’s team_id. A signal
whose payload lacks the field, or holds it empty, groups under an empty
groupKey.
Payload keys must be declared for grouping before they can be used. An
undeclared key returns 400 DIMENSION_NOT_DECLARED and names the keys that
are declared. Contact Paid to declare a new one.
The other bucket
A grouped request returns the busiest dimensionLimit groups (10 by default,
100 at most) and folds every other group into a single series under the key
other. Nothing is dropped: the counts in other are the counts of all the
groups it stands for, so the periods still total correctly.
This is what keeps a grouped chart readable. One organization sends signals across more than a thousand teams; ungrouped, a 90-day daily series over those would be roughly 200,000 points.
A payload value that is itself literally other is counted in the same bucket
as the remainder.
Filtering
Paging
Results are paged explicitly and are never truncated in silence. When more
points exist, pagination.hasMore is true and pagination.nextCursor holds
an opaque cursor. Pass it back as cursor with every other parameter
unchanged:
CSV
Add format=csv for the same points as a text/csv table with one header row:
A CSV has nowhere to carry a cursor, and a response header is not readable by
cross-origin browser code or by the generated CLI and MCP clients. So a CSV is
returned whole or refused: a window whose result does not fit one page returns
400 CSV_RESULT_TRUNCATED rather than a file quietly missing rows. Narrow the
window, lower dimensionLimit, raise limit, request format=json to page, or
start a batch export for a file.
Window alignment
startDate and endDate must fall on whole UTC hours, because the summary
reads an hourly rollup. A window starting at 06:30 has no answer: its first
bucket covers 06:00 to 06:59, so counting it would include signals before the
window and dropping it would lose signals inside one. A mid-hour bound returns
400 WINDOW_NOT_HOUR_ALIGNED rather than a total that silently describes a
different window.
A date-only value always satisfies this — 2026-08-01 is midnight, and as an
endDate it means through 23:59:59.999, the final instant of that day’s last
hour.
Comparing two periods
There is no compareToPrevious parameter. Call the endpoint twice with the two
windows — it is cheap enough that a second call costs less than the ambiguity
of one endpoint answering about two periods at once.