Per-customer API call analytics

Group api_request events by customer_id over 30 days with daily breakdown. The view usage-based pricing needs but most teams cobble together from logs.

Published

difficulty intermediate · time to value 3 minutes · tools used 2

Just say this

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.

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

You

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.

Agent

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" }
  ]
}
Agent

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 }
  ]
}
Agent

Two interesting cases worth flagging to CS/Sales right now:

  • stark_ind: 6.2M calls / month, but active_days = 28 and 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_…" }
Agent

Dashboard live. Recommended follow-ups:

  1. api-usage-as-upsell-signal — find the customers whose calls grew >30% MoM (= ready to upsell)
  2. rate-limit-alert-routing — auto-page when any customer crosses 80% of their cap
  3. 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.”

Try this recipe in your own agent.

Paste the prompt above into your agent. It'll set up Agentry against your data, then run the recipe and show you the real output.

Install https://agentry.sh/agentry.md and set it up