Pay-per-use Metering
Track custom features (API calls, storage, minutes used) and bill overage automatically.
Pay-per-use Metering
Apps that bill on usage — API calls, storage GB, processed jobs, minutes used, AI tokens consumed — record events to WolfieAuth’s metering API. The platform aggregates per (orgId, clientId, featureKey, month) and the monthly billing job stacks overage onto the customer’s Stripe subscription.
This is in addition to the per-seat / flat-rate pricing models on Plans & Billing — you can mix them. A “Pro” plan can have both a fixed $99/mo base, 50 included seats, AND 100k included API calls with $0.20/1k overage.
Architecture
┌────────────────┐ POST /api/mgmt/v1/usage/record ┌──────────────────┐
│ Your app │ ──────────────────────────────▶ │ WolfieAuth │
│ (each event) │ X-Wolfie-App-Key: wmk_... │ UsageCounter │
│ │ { featureKey, quantity } │ upsert (month) │
└────────────────┘ ◀────────────────────────────────── └──────────────────┘
{ used, included, overage } │
│
end-of-month
│
▼
┌─────────────────────┐
│ Aggregator job: │
│ overage = max(0, │
│ used - included) │
│ cents = overage × │
│ cents_per_unit │
│ Stack as Stripe │
│ invoice item │
└─────────────────────┘
Define limits on your plan
Plan limits live in MembershipPlan.limits JSON — free-form, you pick the keys. Example for a CRM with API + storage metering:
{
"max_apps": 5,
"included_seats": 10,
"api_calls": {
"included": 100000,
"overage_cents_per_unit": 1
},
"storage_bytes": {
"included": 5368709120,
"overage_cents_per_unit_per_gb": 50
},
"ai_tokens": {
"included": 1000000,
"overage_cents_per_unit": 0.001
}
}
The keys are yours. Pick a convention that’s readable in the admin panel and matches what your code reports.
Mint an API key
In /admin/organizations/<id>#integrations → API Keys → + Add key:
Name: myapp-prod-billing
Permissions: (empty = full access — fine for usage metering)
Token is shown once: wmk_<64 hex chars>. Store in your app’s env:
WOLFIEAUTH_APP_KEY=wmk_...
Record events from your app
TypeScript / JavaScript
import { recordUsage } from "@wolfieauth/sdk-core/usage";
await recordUsage({
issuer: process.env.WOLFIEAUTH_ISSUER!,
appKey: process.env.WOLFIEAUTH_APP_KEY!,
clientId: "acme-myapi", // optional if your org owns one app
featureKey: "api_calls",
quantity: 1,
});
// → { ok: true, used: 12347, included: 100000, overage: 0 }
Express middleware that bills 1 unit per request:
import { recordUsage } from "@wolfieauth/sdk-core/usage";
app.use(async (req, _res, next) => {
// Fire-and-forget — don't block the request on the metering call.
// Production apps should add a retry queue if 100% accuracy matters.
recordUsage({
issuer: process.env.WOLFIEAUTH_ISSUER!,
appKey: process.env.WOLFIEAUTH_APP_KEY!,
featureKey: "api_calls",
}).catch(() => { /* drop silently */ });
next();
});
Direct HTTP (any language)
curl -X POST https://auth.wolfieguard.com/api/mgmt/v1/usage/record \
-H "X-Wolfie-App-Key: wmk_..." \
-H "Content-Type: application/json" \
-d '{"clientId":"acme-myapi","featureKey":"api_calls","quantity":1}'
Response:
{
"ok": true,
"featureKey": "api_calls",
"clientId": "acme-myapi",
"year": 2026,
"month": 5,
"used": 12347,
"included": 100000,
"overage": 0
}
Quantity > 1
For batch operations (e.g. a 5MB upload counted as 5,242,880 storage_bytes):
await recordUsage({
issuer, appKey,
featureKey: "storage_bytes",
quantity: fileSize, // any integer 1..1_000_000
});
Read current usage
import { listUsage } from "@wolfieauth/sdk-core/usage";
const u = await listUsage({ issuer, appKey });
// → { year: 2026, month: 5, rows: [
// { featureKey: "api_calls", used: 12347, included: 100000, overage: 0, ... },
// { featureKey: "storage_bytes", used: 4_111_222_333, included: 5_368_709_120, overage: 0, ... },
// ] }
Build a usage dashboard inside your downstream app:
const usage = await listUsage({ issuer, appKey });
for (const row of usage.rows ?? []) {
const pct = row.included ? Math.round((row.used / row.included) * 100) : null;
console.log(`${row.featureKey}: ${row.used.toLocaleString()} of ${row.included?.toLocaleString() ?? '∞'} (${pct ?? '?'}%)`);
}
Soft limits + overage billing
recordUsage returns 200 even when usage exceeds the included quota — the counter still ticks and overage carries the running excess. The billing job at month-end multiplies overage × overage_cents_per_unit and stacks it on the customer’s Stripe subscription as an invoice item.
This is intentional: the contract is “metering + billing”, NOT “rate limiter + metering”. If your app wants hard cutoffs, gate on the overage field returned by recordUsage:
const r = await recordUsage({ issuer, appKey, featureKey: "api_calls" });
if (r.ok && r.included && r.used > r.included * 1.5) {
return res.status(429).json({ error: "soft_quota_exceeded", retry_after: "next_billing_cycle" });
}
Auto-charging via Stripe
Customers with active Stripe subscriptions get overage charged automatically — the monthly aggregator creates a Stripe invoice item against their subscription, Stripe charges their card on the next renewal. Money flows to your customer’s connected account (the org running the app); WolfieAuth takes its 5% platform fee on top, KSeF-invoiced monthly via wolfiecrm.
For customers WITHOUT a Stripe subscription (e.g. legacy bank-transfer payers), the aggregator falls back to the wolfiecrm Billing API and issues a KSeF invoice with the overage line items. Your customer’s accounting team handles it the same way they handle any other invoice.
Patterns
Per-user metering
Pass the user’s sub from your session into the featureKey itself for per-user breakdowns: featureKey: "api_calls__userX". Note: this can balloon counter rows if you have many users — works for thousands, not for millions. For high-cardinality user-level tracking, aggregate inside your own DB and report top-line numbers to WolfieAuth.
Burst-tolerant batching
Buffer events client-side for ~1 second and submit batched. Saves request overhead at the cost of a small accuracy window:
let pending = 0;
let timer: NodeJS.Timeout | null = null;
function meterApiCall() {
pending++;
if (timer) return;
timer = setTimeout(() => {
const q = pending;
pending = 0;
timer = null;
recordUsage({ issuer, appKey, featureKey: "api_calls", quantity: q }).catch(() => {});
}, 1000);
}
Custom limit shape
When a feature has multiple dimensions (e.g. cost = base + per-byte), the limits[featureKey] value can be a richer object. The aggregator unpacks however your aggregator wants:
{
"ai_tokens": {
"included": 1000000,
"overage_cents_per_unit": 0.001,
"burst_multiplier": 1.5
}
}
WolfieAuth doesn’t dictate the shape — your aggregator code reads plan.limits and computes whatever you want. The included number is the only convention assumed by the built-in monthly billing job.
Customer-org caps (B2B-of-B2B)
If your app supports its own customer organizations (your end-users sign up via self-serve and become SPECIAL_ADMIN of a fresh WolfieAuth Organization tagged with originAppClientId=<your-app>), you can cap how many such orgs your wolfieauth-platform plan includes:
{
"max_customer_orgs": 100,
"overage_customer_org_cents": 500
}
max_customer_orgs: 100— your plan includes 100 customer-orgs.overage_customer_org_cents: 500— $5/month per customer-org over the cap. Stripe invoice item is appended to your wolfieauth-platform sub on month-end.- Omit
overage_customer_org_centsto make the cap a hard limit. The next self-serve signup against your app gets409 customer_org_cap_reacheduntil you upgrade your plan.
Read the live count + cap status at any time:
curl https://auth.wolfieguard.com/admin/api/admin/clients/<id>/customer-orgs?limit=1 \
-H "Cookie: wolfieauth_admin=..."
# → { total: 47, ... }
Or see them in the 🏢 Customer Orgs tab on /admin/clients/<id> — search, filter, soft-revoke per row.
Continue reading
- Plans & Billing for per-seat + flat-rate pricing
- SDKs for the full SDK surface
- Webhooks to receive billing events back
Last updated: