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 integer cents (divide by 100), and UInt64/Int64 results come back as JSON strings.

Usage & signals

Total signals in the last 30 days

1SELECT count() AS signals
2FROM fact_signal
3WHERE created_at > now() - INTERVAL 30 DAY

Signal volume by type

1SELECT signal_name, count() AS signals
2FROM fact_signal
3WHERE created_at > now() - INTERVAL 30 DAY
4GROUP BY signal_name
5ORDER BY signals DESC

Daily signal time-series

1SELECT toDate(created_at) AS day, count() AS signals
2FROM fact_signal
3WHERE created_at > now() - INTERVAL 30 DAY
4GROUP BY day
5ORDER BY day

Signals by customer (resolving names)

1SELECT c.name AS customer, count() AS signals
2FROM fact_signal s
3JOIN dim_customer c ON s.customer_id = c.id
4WHERE s.created_at > now() - INTERVAL 30 DAY
5GROUP BY customer
6ORDER BY signals DESC
7LIMIT 20

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

1SELECT data.opportunity_stage::String AS stage, count() AS signals
2FROM fact_signal
3WHERE created_at > now() - INTERVAL 30 DAY
4GROUP BY stage
5ORDER BY signals DESC

Filter on a metadata field

1SELECT count() AS completed_calls
2FROM fact_signal
3WHERE signal_name = 'call'
4 AND data.call_status::String = 'completed'
5 AND created_at > now() - INTERVAL 7 DAY

Aggregate a numeric metadata field

1SELECT
2 data.account_tier::String AS tier,
3 avg(data.call_length_sec::Float64) AS avg_call_seconds
4FROM fact_signal
5WHERE signal_name = 'call'
6 AND created_at > now() - INTERVAL 30 DAY
7GROUP BY tier

AI cost

Total spend (dollars) by vendor

1SELECT vendor, count() AS calls, sum(cost_amount) / 100 AS dollars
2FROM fact_cost
3WHERE start_time > now() - INTERVAL 30 DAY
4GROUP BY vendor
5ORDER BY dollars DESC

Spend by model with token usage

1SELECT
2 model,
3 count() AS calls,
4 sum(input_tokens) AS input_tokens,
5 sum(output_tokens) AS output_tokens,
6 sum(cost_amount) / 100 AS dollars
7FROM fact_cost
8WHERE start_time > now() - INTERVAL 30 DAY
9GROUP BY model
10ORDER BY dollars DESC

Daily cost trend

1SELECT toDate(start_time) AS day, sum(cost_amount) / 100 AS dollars
2FROM fact_cost
3WHERE start_time > now() - INTERVAL 30 DAY
4GROUP BY day
5ORDER BY day

Cost by customer

1SELECT c.name AS customer, sum(co.cost_amount) / 100 AS dollars
2FROM fact_cost co
3JOIN dim_customer c ON co.customer_id = c.id
4WHERE co.start_time > now() - INTERVAL 30 DAY
5GROUP BY customer
6ORDER BY dollars DESC
7LIMIT 20

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.

1SELECT
2 s.signal_name,
3 count(DISTINCT s.signal_id) AS signals,
4 sum(co.cost_amount) / 100 AS dollars,
5 sum(co.cost_amount) / 100 / count(DISTINCT s.signal_id) AS dollars_per_signal
6FROM fact_signal s
7JOIN fact_cost co ON s.trace_id = co.trace_id
8WHERE s.created_at > now() - INTERVAL 30 DAY
9GROUP BY s.signal_name
10ORDER BY dollars DESC

Revenue

Monthly revenue (posted invoices)

1SELECT
2 toStartOfMonth(issue_date) AS month,
3 sum(total_incl_tax) / 100 AS revenue
4FROM fact_invoice
5WHERE status = 'posted'
6 AND issue_date > now() - INTERVAL 12 MONTH
7GROUP BY month
8ORDER BY month

Revenue by customer

1SELECT c.name AS customer, sum(i.total_incl_tax) / 100 AS revenue
2FROM fact_invoice i
3JOIN dim_customer c ON i.customer_id = c.id
4WHERE i.status = 'posted'
5GROUP BY customer
6ORDER BY revenue DESC
7LIMIT 20

Revenue by product (invoice lines → order lines → products)

1SELECT p.name AS product, sum(il.total_after_tax) / 100 AS revenue
2FROM fact_invoice_line il
3JOIN fact_order_line ol ON il.order_line_id = ol.id
4JOIN dim_product p ON ol.product_id = p.id
5GROUP BY product
6ORDER BY revenue DESC

Outstanding / overdue invoices

1SELECT c.name AS customer, sum(i.amount_due) / 100 AS outstanding
2FROM fact_invoice i
3JOIN dim_customer c ON i.customer_id = c.id
4WHERE i.payment_status IN ('pending', 'partiallyPaid', 'overdue')
5GROUP BY customer
6ORDER BY outstanding DESC

Payments / cash flow

1SELECT toDate(payment_date) AS day, sum(amount) / 100 AS collected
2FROM fact_payment
3WHERE payment_status = 'posted'
4 AND payment_date > now() - INTERVAL 30 DAY
5GROUP BY day
6ORDER BY day

Billed usage

Which signals drive billed revenue

1SELECT signal_name, sum(subtotal) / 100 AS billed
2FROM fact_usage_summary
3WHERE status = 'billed'
4GROUP BY signal_name
5ORDER BY billed DESC

Overage (usage above the included allowance)

1SELECT
2 signal_name,
3 sum(signal_count) AS used,
4 sum(included_quantity) AS included,
5 sum(greatest(signal_count - included_quantity, 0)) AS overage
6FROM fact_usage_summary
7WHERE included_quantity IS NOT NULL
8GROUP BY signal_name

Credits

Net credit flow by currency

1SELECT cc.name AS currency, sum(t.amount) AS net_credits
2FROM fact_credit_transaction t
3JOIN dim_credits_currency cc ON t.credits_currency_id = cc.id
4GROUP BY currency

Top credit-spending signals

1SELECT signal_name, -sum(amount) AS credits_spent
2FROM fact_credit_transaction
3WHERE type = 'SPEND'
4 AND created_at > now() - INTERVAL 30 DAY
5GROUP BY signal_name
6ORDER BY credits_spent DESC

Delivered value

Value delivered by type

1SELECT
2 value_type_name,
3 sum(delivered_value_cents) / 100 AS value_dollars,
4 sum(unit_value) AS total_units
5FROM fact_delivered_value
6WHERE created_at > now() - INTERVAL 30 DAY
7GROUP BY value_type_name
8ORDER BY value_dollars DESC

Value vs. cost (ROI) by customer

1SELECT
2 c.name AS customer,
3 sum(dv.delivered_value_cents) / 100 AS value_dollars
4FROM fact_delivered_value dv
5JOIN dim_customer c ON dv.customer_id = c.id
6WHERE dv.created_at > now() - INTERVAL 30 DAY
7GROUP BY customer
8ORDER BY value_dollars DESC
9LIMIT 20

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:

1SELECT signal_id, signal_name, created_at
2FROM fact_signal
3WHERE created_at < '2026-06-01 00:00:00' -- last value from the previous page
4ORDER BY created_at DESC
5LIMIT 1000