API quota usage as engagement and upsell timing

For each customer on a usage-based plan, show daily call volume as % of cap. Flag accounts approaching 80% — the perfect upgrade moment.

Published

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

Just say this

For each customer on an API-call quota, show their daily usage as % of cap. Flag accounts approaching 80% — upsell trigger.

Why this matters

API-quota pricing turns engineering into a sales-influencing function. Every call your customer makes is both a feature use AND a step toward needing more. The hot signal — “this customer is about to hit the cap” — is the warmest upsell moment in your business. They’re already paying, they’ve already integrated, they’re feeling the limit.

Stripe Metering can compute usage but doesn’t surface “approaching limit” alerts in a usable form. Most teams find out a customer hit their cap when the customer gets a 429 and emails support angry.

Agentry computes per-account quota utilization in one HogQL query and ranks accounts by “about to hit the wall.” Sales gets the call list; engineering gets the signal too (capacity planning).

What you get

  • A per-account utilization table: account | quota | used_today | used_mtd | pct_of_cap | days_to_cap_at_current_rate
  • The accounts crossing thresholds (50/80/95/100%) sorted by how soon they’ll hit
  • A draft email per top-3 to the account’s primary contact: “you’re at 82% — here’s how upgrading helps”

Walk through it

You

For each customer on an API-call quota, show their daily usage as % of cap. Flag accounts approaching 80% — upsell trigger.

Agent

Computing per-account API call counts and joining against current quota.

agentry_analytics_query {
  project_id: "default",
  hogql: "WITH quotas AS (SELECT properties.account_id AS aid, argMax(properties.api_quota_monthly, timestamp) AS quota FROM events WHERE event = 'quota_set' GROUP BY aid HAVING quota > 0), usage_mtd AS (SELECT properties.account_id AS aid, count() AS calls_mtd FROM events WHERE event = 'api_call' AND timestamp >= toStartOfMonth(now()) GROUP BY aid), usage_today AS (SELECT properties.account_id AS aid, count() AS calls_today FROM events WHERE event = 'api_call' AND timestamp >= toStartOfDay(now()) GROUP BY aid), days_passed AS (SELECT dateDiff('day', toStartOfMonth(now()), now()) + 1 AS days_in_mtd, dateDiff('day', toStartOfMonth(now()), toStartOfMonth(now() + INTERVAL 1 MONTH)) AS days_in_month) SELECT q.aid AS account, q.quota AS quota, COALESCE(u.calls_mtd, 0) AS used_mtd, COALESCE(t.calls_today, 0) AS used_today, round(COALESCE(u.calls_mtd, 0) * 100.0 / q.quota, 1) AS pct_of_cap, round(COALESCE(u.calls_mtd, 0) * 1.0 / d.days_in_mtd * d.days_in_month, 0) AS projected_eom, round(q.quota * 1.0 / nullIf(COALESCE(u.calls_mtd, 0) * 1.0 / d.days_in_mtd, 0)) AS days_to_cap_at_current_rate FROM quotas q LEFT JOIN usage_mtd u ON q.aid = u.aid LEFT JOIN usage_today t ON q.aid = t.aid CROSS JOIN days_passed d HAVING pct_of_cap >= 50 ORDER BY pct_of_cap DESC"
}
{
  "rows": [
    { "account": "acct_megacorp", "quota": 1000000, "used_mtd": 982400, "used_today": 41200, "pct_of_cap": 98.2, "projected_eom": 1340000, "days_to_cap_at_current_rate": 0 },
    { "account": "acct_innovate", "quota": 500000,  "used_mtd": 412000, "used_today": 18400, "pct_of_cap": 82.4, "projected_eom": 562000,  "days_to_cap_at_current_rate": 4 },
    { "account": "acct_acme",     "quota": 100000,  "used_mtd": 71000,  "used_today": 3100,  "pct_of_cap": 71.0, "projected_eom": 96900,   "days_to_cap_at_current_rate": 9 },
    { "account": "acct_globex",   "quota": 250000,  "used_mtd": 142000, "used_today": 4800,  "pct_of_cap": 56.8, "projected_eom": 194000,  "days_to_cap_at_current_rate": 22 }
  ]
}
Agent

Three accounts in the upgrade zone:

  1. megacorp — 98.2%, ALREADY OVER projected. They’ll hit the cap today or tomorrow. Their 429s are about to start; sales should call now, not after the angry email.
  2. innovate — 82.4%, ~4 days to cap. Perfect warm-lead moment. Reach out before they feel the pain.
  3. acme — 71%, ~9 days to cap. Worth a CSM “heads up, you’re trending toward your cap, want to discuss next tier?” message.

Globex at 57% is fine; mention in next QBR. Let me draft the megacorp + innovate emails:


To: [email protected] Subject: Heads up — you’re at 98% of your monthly API quota

Hi —

Your team’s pace this month puts you above your 1M-call cap by end of the month, likely as soon as tomorrow. Two options:

  1. Bump to the next tier (5M calls/mo) — same per-call price, headroom for growth.
  2. Stay on this tier and accept rate-limiting once you hit the cap.

Happy to set up a quick call to discuss which fits.

The output

API Quota Utilization (current month)

ACCOUNT         QUOTA      USED MTD    %     PROJ EOM    DAYS-TO-CAP
megacorp       1.0M        982,400    98%    1.34M           0  ★ CRITICAL
innovate       500k        412,000    82%      562k           4  ★ UPSELL NOW
acme           100k         71,000    71%      96.9k           9     warm
globex         250k        142,000    57%      194k          22     ok

ACTIONS:
  ★ megacorp:    AE call TODAY — they will exceed cap tomorrow
                  Draft email above
  ★ innovate:    AE outreach this week — warm lead window
  ★ acme:        CSM heads-up message
    globex:      track for next month

REVENUE OPPORTUNITY:
  If all 3 upgrade to next tier, +$3,500-$6,000 MRR.
  Cost of waiting: customer hits 429, fires angry email, downgrades or churns.

Setting it up

Two events needed:

1. Every API call as an analytics event (sampling OK if volume is huge):

// In your API middleware
app.use(async (req, res, next) => {
  await fetch(`https://api.agentry.sh/v1/analytics/${process.env.AGENTRY_PROJECT_ID}/`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.AGENTRY_DSN}`,
      "Content-Type": "application/json",
      "User-Agent": "myapp-api/1.0",  // REQUIRED — Cloudflare 403s default UAs
    },
    body: JSON.stringify({
      event: "api_call",
      distinct_id: req.account.id,
      properties: {
        account_id: req.account.id,
        endpoint: req.path,
        // skip waiting on this — fire-and-forget
      },
    }),
  }).catch(() => {});  // never break the request
  next();
});

At >1M calls/day, sample at 1-in-N and divide quota by N in the query — same shape, less ingest cost.

2. Quota changes (set or updated):

// On plan change OR explicit quota bump
await fetch(`https://api.agentry.sh/v1/analytics/${process.env.AGENTRY_PROJECT_ID}/`, {
  method: "POST",
  headers: { /* same */ "User-Agent": "myapp/1.0" },
  body: JSON.stringify({
    event: "quota_set",
    distinct_id: account.id,
    properties: {
      account_id: account.id,
      api_quota_monthly: account.plan.quota,
      plan: account.plan.name,
    },
  }),
});

Variations

  • “Same query for endpoint-level usage — which endpoints are the heaviest? Maybe there’s a more efficient API to suggest.”
  • “Find accounts that hit 100% last month — did they upgrade, downgrade, or just absorb the 429s?”
  • “For each over-cap account, compute the dollar value of their overage at the next tier’s rate. Use it in the sales pitch.”
  • “Daily cron at 8am: post any account that crossed 80% in the last 24h to #sales-pipeline.” (uses a Routine)

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