Analytics query cookbook
Copy-paste recipes for common questions. Each is the query value you send to
POST /query. See the Schema reference for every column.
Reminder: monetary columns are cents (divide by 100;
fact_cost.cost_amount is fractional cents), credit amounts are decimal
counts of credits (never divided by 100), UInt64/Int64 results come back
as JSON strings, and Decimal results come back as JSON numbers (wrap
in toString() for exact digits beyond 2^53).
Prerequisites
- A Paid API key with access to your organization’s Analytics API.
- Signals, costs, delivered value, invoices, or credit activity already present for the tables you want to query.
- Familiarity with the table names and joins in the Schema reference.
Usage and signals
Total signals in the last 30 days
Signal volume by type
Daily signal time-series
Signals by customer (resolving names)
Querying signal metadata
Signals carry a free-form JSON data payload. Access a field with
data.<path> and cast it to a type. First discover available keys with
GET /signals/metadata, then filter or group on them.
Casting a missing key with ::String yields an empty string (''), not
NULL, so a '' bucket in a grouped result represents signals that don’t
carry that key.
Group by a string metadata field
Filter on a metadata field
Aggregate a numeric metadata field
AI cost
cost_amount is exact fractional cents (a typical AI call costs a fraction of
a cent), so SUM(cost_amount) / 100 gives exact dollars and matches the Cost
Explorer in the Paid UI.
Total spend (dollars) by vendor
Spend by model with token usage
Daily cost trend
Cost by customer
Cost per signal (joining usage and cost)
Signals and costs share a trace_id, so you can attribute AI cost to the
signal that incurred it.
Revenue
Monthly revenue (posted invoices)
Revenue by customer
Revenue by product (invoice lines → order lines → products)
Fixed-fee vs usage revenue split (posted invoices)
charge_type is usage for metered lines and oneTime/recurring/seatBased
for fixed charges. It is empty on manual lines (no linked pricing) and on lines
predating charge-type attribution. Bucket those as unknown rather than folding
them into fixed, or they will inflate the fixed-fee share.
A non-trivial unknown total means manual or unattributed lines carry real revenue.
Inspect them before treating the fixed-vs-usage split as complete. For the full
breakdown, group by il.charge_type directly
(oneTime / recurring / seatBased / usage, plus empty for unattributed).
Outstanding / overdue invoices
Payments and cash flow
Billed usage
Which signals drive billed revenue
Counts every window billed so far, including those closed since September 17,
2026, which this table showed as collecting until October 2026. Collecting
windows have no subtotal yet.
Overage (usage above the included allowance)
Credits
Credit amounts and balances are Decimal(30, 6), not integers: a single spend
can be a fraction of a credit, and they are counts of credits rather than cents,
so never divide them by 100. As with fractional cents, round after
aggregating rather than per row.
Net credit flow by currency
Top credit-spending signals
Credit consumption by user within a customer
If your signals include external_user_id in the data payload (see
First signals),
you can break down credit spend per user:
Customer units
Customer units are the teams, departments, or users inside a customer. Join
dim_customer_unit to label usage and credit spend by unit, and
dim_customer_unit_ancestor to total a unit with everything below it. Always
use LEFT JOIN and keep the (unassigned) row: it is real usage that was not
attributed to a unit, and dropping it makes unit totals disagree with the
customer total.
Credit consumption by unit for one customer
Group by the unit’s key and the currency’s id as well as their names: two units, or two credit currencies, can share a name.
Signals by unit
A signal names its unit by customer_unit_id, or only by
external_customer_unit_id if the unit did not exist yet when the signal
arrived. Join twice and prefer the first match:
Credit consumption per top-level unit, including the units below it
Each row counts once for every unit above it, so pick one level, as the
parent_id filter does here, rather than summing nested units together. The
a.depth = 0 line adds one row for usage booked on the root itself; without it
that usage is left out.
Spend caps
A spend cap limits how many credits of one credits currency a customer unit
and every unit below it can spend per period. dim_customer_unit_cap_current
holds the cap in force now on each unit, with its current period
(period_start, period_end). The usage counted against a cap is the net
SPEND of its credits currency by the unit’s whole subtree, from
period_start up to the earlier of period_end and effective_until.
Caps are spend policy, so the cap tables need billing access, as the cap
endpoints do. Through the Analytics API they return rows only for an API key
with read:billing (or a dashboard user); for any other key they return no
rows. In a custom view they return rows only when the view has
capsEnabled: true.
Cap usage so far this period
This gives the same used and remaining as the customer unit cap endpoints
(currentPeriod.used):
The LEFT JOIN keeps caps with no spend yet. remaining goes negative once a
unit is over its cap.
Caps near their limit across customers
Drop the customer filter from caps in the query above and replace its final
SELECT with:
The cap in force at another time
dim_customer_unit_cap keeps every version. The one in force at an instant is
the highest version whose window covers it:
Unit history
fact_customer_unit_event has one row per change to a unit: created, renamed,
made billable or not, moved, or deleted. changed_fields names what changed.
Who made the change and unit metadata values are not included.
What changed in a unit’s tree last week
The tree is today’s tree: a unit moved out of it during the week is not listed,
and one moved into it is listed with its earlier history. Order by version
rather than created_at to replay changes in the order they happened.
Delivered value
delivered_value_cents is denominated in each row’s
delivered_value_currency, which is not always USD: organizations using
value models can deliver value in EUR, GBP, and other currencies. Always group
by (or filter on) delivered_value_currency; summing cents across currencies
produces meaningless totals. Cents are exact and may carry fractions, so round
after aggregating.
Value delivered by type
Value vs. cost (ROI) by customer
Paginating large result sets
Results are capped at 10,000 rows. For larger extracts, page with a WHERE
filter on an ordered column (keyset pagination) rather than a large OFFSET:
Related
- Analytics API overview for endpoints, limits, and errors.
- Schema reference for every table and column you can query.