Why this matters
If you sell a usage-based API, the per-customer usage table is your second-most-important dashboard (after MRR). Yet most teams build it badly:
- The data lives in CloudFlare logs or application logs that aren’t queryable per-customer
- The “dashboard” is a quarterly SQL someone ran by hand
- CS can’t pull “how much did Acme use last week?” without pinging engineering
- Sales has no idea who’s growing usage (= ready to upsell) vs flatlining (= about to churn)
Agentry treats API requests as just-another-analytics-event. One HogQL pass gives you top customers, daily series, growth rate — the lot. Then publish it as a CS-visible dashboard so the team doesn’t have to ping engineering for every account check.
What you get
- Top 20 customers by 30-day call volume
- Daily breakdown per customer (sparkline data)
- A published dashboard that CS and sales can both pull up
Walk through it
Group api_request events by customer_id in the last 30 days. Show me top 20 customers by call volume, with daily breakdown for each.
Two passes — first the ranked top 20, then daily series for each.
agentry_analytics_query {
project_id: "default",
query: "
select
properties.customer_id as customer_id,
count() as total_calls,
count(distinct toDate(timestamp)) as active_days,
max(timestamp) as last_call_at
from events
where event = 'api_request' and timestamp > now() - interval 30 day
group by customer_id
order by total_calls desc
limit 20
"
}
{
"rows": [
{ "customer_id": "acme_corp", "total_calls": 18420000, "active_days": 30, "last_call_at": "2026-05-16T13:42:00Z" },
{ "customer_id": "wayne_ent", "total_calls": 9842000, "active_days": 30, "last_call_at": "2026-05-16T13:41:00Z" },
{ "customer_id": "stark_ind", "total_calls": 6210000, "active_days": 28, "last_call_at": "2026-05-15T22:18:00Z" },
{ "customer_id": "umbrella_co", "total_calls": 4810000, "active_days": 30, "last_call_at": "2026-05-16T13:30:00Z" },
{ "customer_id": "initech_llc", "total_calls": 180000, "active_days": 8, "last_call_at": "2026-05-04T10:00:00Z" }
]
}
Now the daily series for the top customers so you can see the shape.
agentry_analytics_query {
project_id: "default",
query: "
select
properties.customer_id as customer_id,
toDate(timestamp) as day,
count() as calls
from events
where event = 'api_request'
and timestamp > now() - interval 30 day
and properties.customer_id in ('acme_corp', 'wayne_ent', 'stark_ind', 'umbrella_co', 'initech_llc')
group by customer_id, day
order by customer_id, day
"
}
{
"rows": [
{ "customer_id": "acme_corp", "day": "2026-04-16", "calls": 580000 },
{ "customer_id": "acme_corp", "day": "2026-04-17", "calls": 604000 },
{ "customer_id": "acme_corp", "day": "2026-04-18", "calls": 590000 },
"...",
{ "customer_id": "initech_llc", "day": "2026-04-29", "calls": 12000 },
{ "customer_id": "initech_llc", "day": "2026-05-04", "calls": 168000 },
{ "customer_id": "initech_llc", "day": "2026-05-05", "calls": 0 }
]
}
Two interesting cases worth flagging to CS/Sales right now:
stark_ind: 6.2M calls / month, butactive_days = 28and last call 2 days ago. They’ve gone quiet. Either weekend lull or churn risk. Worth a CS check-in.initech_llc: 180k total but only 8 active days, last call 12 days ago. Classic dormant API customer — they integrated, hit it once big, then ghosted. A “did we miss anything?” email here is high-leverage.
Let me publish the rolling top-20 as a dashboard.
agentry_publish_query {
recipe_id: "raw_hogql",
description: "Top 20 API customers by 30d volume",
params: { query: "<first query above>" }
}
{ "publication_id": "pub_api_top20", "public_url": "https://agentry.sh/q/pub_api_top20?key=agp_…" }
Dashboard live. Recommended follow-ups:
api-usage-as-upsell-signal— find the customers whose calls grew >30% MoM (= ready to upsell)rate-limit-alert-routing— auto-page when any customer crosses 80% of their cap- Schedule a weekly Routine that emails CS the deltas
The output
Top 20 API customers (30d)
Customer Calls (30d) Active days Last call Trend
─────────────────────────────────────────────────────────────────────
acme_corp 18,420,000 30 2 min ago steady █████
wayne_ent 9,842,000 30 3 min ago growing ███▟
stark_ind 6,210,000 28 2 days ago ★ check in
umbrella_co 4,810,000 30 15 min ago steady ████
initech_llc 180,000 8 12 days ago ★★ dormant
Public dashboard:
https://agentry.sh/q/pub_api_top20?key=agp_xxx
Suggested next investigations:
- api-usage-as-upsell-signal (who's growing?)
- rate-limit-alert-routing (who's hitting caps?)
- weekly Routine: post deltas to #cs every Monday
Setting it up
Emit one api_request analytics event per request. Don’t worry about volume — Agentry’s analytics ingest handles millions/day per project. The trick is what to put in customer_id:
- For API-key auth: the customer (org/project) the key belongs to, NOT the raw key
- For OAuth: the OAuth client id
- For Bearer JWT: a sub-claim you control
Tag a few useful properties for downstream segmentation:
// Hono / Express middleware after auth
app.use(async (c, next) => {
const customer = await resolveCustomer(c.req.header("authorization"));
c.set("customer", customer);
// emit analytics — DO NOT await this, send fire-and-forget
fetch(`https://api.agentry.sh/v1/analytics/${PROJECT_ID}/`, {
method: "POST",
headers: {
"Authorization": `Bearer ${env.AGENTRY_DSN}`,
"Content-Type": "application/json",
"User-Agent": "myapi-edge/1.0", // REQUIRED — Cloudflare 403s default UAs
},
body: JSON.stringify({
event: "api_request",
distinct_id: customer.id,
properties: {
customer_id: customer.id,
plan: customer.plan,
endpoint: c.req.routePath,
method: c.req.method,
sdk_version: c.req.header("x-sdk-version"),
ip_country: c.req.header("cf-ipcountry"),
},
}),
}).catch(() => {}); // never block the user request on analytics
await next();
});
Per-request volume warning: at very high RPS (>10k/sec) consider batching events client-side and POSTing a compacted batch every few seconds. The /v1/analytics/ endpoint accepts an array of events in one POST.
Variations
- “Same top-20 but split by API endpoint — which endpoints are the heavy hitters?”
- “Hour-of-day distribution for the top 5 customers. Are they bursty or steady-state?”
- “Per-customer error rate (4xx + 5xx ÷ total). Sometimes a top customer’s usage is mostly failing requests.”
- “Show me customers whose usage dropped >50% in the last 7 days vs prior 7. Early churn signals.”