Metering Module
Token usage tracking and quota enforcement
The Metering module tracks LLM token consumption and enforces per-user daily quotas. Source: packages/server/src/modules/metering/.
REST Endpoints
All endpoints are prefixed with /api/v1/metering and require ClerkAuthGuard.
| Method | Path | Description |
|---|---|---|
| GET | /metering/org | Org-level usage for billing period |
| GET | /metering/user | User's usage for the current billing period |
| GET | /metering/quota | User's current quota status |
| DELETE | /metering/reset | Reset today's usage records (debug only) |
Metering Service (services/metering.service.ts)
recordLLMUsage(params)
Called by ChatService after every LLM response. Parameters:
| Field | Type | Description |
|---|---|---|
orgId | string | Organisation |
userId | string | User who made the request |
model | string | Model ID (e.g. claude-sonnet-4-20250514) |
inputTokens | number | Prompt tokens |
outputTokens | number | Completion tokens |
metadata | JSON? | Extra context (session ID, workspace, etc.) |
Cost is calculated based on model pricing from @cosmohq/core.
checkQuota(orgId, userId?)
Returns the current quota status. Called by QuotaGuard before every chat request.
Response:
{
allowed: boolean;
usedTokens: number;
limitTokens: number;
percentage: number; // 0–100
plan: Plan;
}Orgs whose plan has tokensPerUserPerDay === -1 always return allowed: true (unlimited quota). Note: the ENTERPRISE plan currently has a fixed daily limit of 10M tokens, same as PREMIUM.
getOrgUsageSummary(orgId)
Aggregates token usage for the org across the current billing period:
{
totalInputTokens: number;
totalOutputTokens: number;
totalTokens: number;
totalCostUsd: number;
byModel: { model: string; tokens: number; cost: number }[];
byUser: { userId: string; tokens: number; cost: number }[];
}getUserUsageSummary(orgId, userId)
Today's usage for the requesting user:
{
usedTokens: number;
limitTokens: number;
percentage: number;
inputTokens: number;
outputTokens: number;
costUsd: number;
}resetDailyUsage(orgId, userId?)
Deletes all UsageRecord rows for today. If userId is provided, deletes only that user's records. Used for debug/testing.
Quota Limits by Plan
| Plan | Daily Token Limit (per user) |
|---|---|
NO_PLAN | 0 (blocked) |
FREE | 1,000,000 |
STANDARD | 2,000,000 |
PREMIUM | 10,000,000 |
ENTERPRISE | 10,000,000 |
Quotas reset at midnight UTC each day (records are scoped by date in the query).
Usage Record Schema
| Field | Type | Description |
|---|---|---|
id | string | Primary key |
orgId | string | Organisation |
userId | string | User |
type | UsageType enum | Usage category (LLM_CHAT, LLM_AGENT, etc.) |
inputTokens | int | Input tokens |
outputTokens | int | Output tokens |
model | string? | Model ID (nullable) |
costUsd | Decimal | Cost in USD |
metadata | JSON? | Extra context |
createdAt | DateTime | Usage timestamp (used for date filtering) |
QuotaGuard
packages/server/src/common/guards/quota.guard.ts is applied to POST /api/v1/chat:
- Calls
MeteringService.checkQuota(orgId, userId). - If
allowed: false, throwsForbiddenException→HTTP 403with the quota status payload. - If
allowed: true, the request proceeds to the chat handler.