Marketplace

usage-metering

Usage tracking, metering infrastructure, aggregation patterns, and consumption-based billing support for SaaS applications

allowed_tools: Read, Glob, Grep, Task, mcp__perplexity__search, mcp__microsoft-learn__microsoft_docs_search, mcp__context7__query-docs

$ Installer

git clone https://github.com/melodic-software/claude-code-plugins /tmp/claude-code-plugins && cp -r /tmp/claude-code-plugins/plugins/saas-patterns/skills/usage-metering ~/.claude/skills/claude-code-plugins

// tip: Run this command in your terminal to install the skill


name: usage-metering description: Usage tracking, metering infrastructure, aggregation patterns, and consumption-based billing support for SaaS applications allowed-tools: Read, Glob, Grep, Task, mcp__perplexity__search, mcp__microsoft-learn__microsoft_docs_search, mcp__context7__query-docs

Usage Metering Skill

Guidance for implementing usage tracking and metering infrastructure in SaaS applications.

MANDATORY: Documentation-First Approach

Before implementing usage metering:

  1. Invoke docs-management skill for billing and metering patterns
  2. Verify patterns via MCP servers (perplexity for current best practices, microsoft-learn for Azure patterns)
  3. Base all guidance on official documentation and current industry standards

When to Use

  • Implementing consumption-based pricing
  • Tracking API calls, storage, compute, or feature usage
  • Building usage dashboards and alerts
  • Integrating with billing systems for usage-based charges

Core Concepts

Usage Event Types

Usage Categories:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Category        โ”‚ Examples                   โ”‚ Billing Unit โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ API Calls       โ”‚ Requests, queries, ops     โ”‚ Per 1K calls โ”‚
โ”‚ Compute         โ”‚ CPU seconds, GPU hours     โ”‚ Per hour     โ”‚
โ”‚ Storage         โ”‚ GB stored, objects         โ”‚ Per GB/month โ”‚
โ”‚ Data Transfer   โ”‚ Egress, ingress bytes      โ”‚ Per GB       โ”‚
โ”‚ Seats/Users     โ”‚ Active users, MAU          โ”‚ Per user     โ”‚
โ”‚ Features        โ”‚ Reports generated, exports โ”‚ Per action   โ”‚
โ”‚ Resources       โ”‚ Projects, environments     โ”‚ Per resource โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Metering Patterns

Pattern Selection:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Pattern          โ”‚ Use When                  โ”‚ Trade-offs   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Real-time        โ”‚ Need instant visibility   โ”‚ Higher cost  โ”‚
โ”‚ Near-real-time   โ”‚ 5-15 min latency OK       โ”‚ Balanced     โ”‚
โ”‚ Batch            โ”‚ Daily/hourly aggregation  โ”‚ Lower cost   โ”‚
โ”‚ Sampling         โ”‚ High volume, estimates OK โ”‚ Approximate  โ”‚
โ”‚ Hybrid           โ”‚ Mix of above              โ”‚ Complex      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Implementation Patterns

Usage Event Model

// Core usage event structure
public sealed record UsageEvent
{
    public required Guid EventId { get; init; } = Guid.NewGuid();
    public required Guid TenantId { get; init; }
    public required string MetricName { get; init; }  // e.g., "api.requests"
    public required decimal Quantity { get; init; }
    public required string Unit { get; init; }        // e.g., "count", "bytes"
    public required DateTimeOffset Timestamp { get; init; }
    public required string Source { get; init; }      // e.g., "api-gateway"
    public string? UserId { get; init; }
    public string? ResourceId { get; init; }
    public Dictionary<string, string> Dimensions { get; init; } = [];
    public string? IdempotencyKey { get; init; }
}

// Aggregated usage for billing periods
public sealed record UsageAggregate
{
    public required Guid TenantId { get; init; }
    public required string MetricName { get; init; }
    public required DateOnly PeriodStart { get; init; }
    public required DateOnly PeriodEnd { get; init; }
    public required decimal TotalQuantity { get; init; }
    public required string Unit { get; init; }
    public required int EventCount { get; init; }
    public DateTimeOffset LastUpdated { get; init; }
}

Metering Service Interface

public interface IUsageMeteringService
{
    // Record single usage event
    Task RecordUsageAsync(
        Guid tenantId,
        string metricName,
        decimal quantity,
        string unit,
        Dictionary<string, string>? dimensions = null,
        string? idempotencyKey = null,
        CancellationToken ct = default);

    // Record batch of events
    Task RecordBatchAsync(
        IReadOnlyList<UsageEvent> events,
        CancellationToken ct = default);

    // Get current period usage
    Task<UsageAggregate> GetCurrentUsageAsync(
        Guid tenantId,
        string metricName,
        CancellationToken ct = default);

    // Get historical usage
    Task<IReadOnlyList<UsageAggregate>> GetUsageHistoryAsync(
        Guid tenantId,
        string metricName,
        DateOnly startDate,
        DateOnly endDate,
        CancellationToken ct = default);
}

High-Volume Ingestion Pattern

// Buffered writer for high-throughput scenarios
public sealed class BufferedUsageWriter : IAsyncDisposable
{
    private readonly Channel<UsageEvent> _channel;
    private readonly IUsageEventStore _store;
    private readonly Task _processorTask;

    public BufferedUsageWriter(
        IUsageEventStore store,
        int batchSize = 100,
        TimeSpan? flushInterval = null)
    {
        _store = store;
        _channel = Channel.CreateBounded<UsageEvent>(
            new BoundedChannelOptions(10_000)
            {
                FullMode = BoundedChannelFullMode.Wait
            });

        flushInterval ??= TimeSpan.FromSeconds(5);
        _processorTask = ProcessEventsAsync(batchSize, flushInterval.Value);
    }

    public ValueTask RecordAsync(UsageEvent evt, CancellationToken ct = default)
        => _channel.Writer.WriteAsync(evt, ct);

    private async Task ProcessEventsAsync(int batchSize, TimeSpan flushInterval)
    {
        var batch = new List<UsageEvent>(batchSize);
        var timer = new PeriodicTimer(flushInterval);

        while (await _channel.Reader.WaitToReadAsync())
        {
            // Drain available events up to batch size
            while (batch.Count < batchSize &&
                   _channel.Reader.TryRead(out var evt))
            {
                batch.Add(evt);
            }

            if (batch.Count >= batchSize || await ShouldFlushAsync(timer))
            {
                await _store.WriteBatchAsync(batch);
                batch.Clear();
            }
        }

        // Flush remaining on shutdown
        if (batch.Count > 0)
            await _store.WriteBatchAsync(batch);
    }
}

Architecture Options

Event Streaming Architecture

High-Volume Metering:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ API Gateway โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Event Hub/  โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Stream      โ”‚
โ”‚ (emit)      โ”‚     โ”‚ Kafka       โ”‚     โ”‚ Processor   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                               โ”‚
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚                          โ”‚                  โ”‚
                    โ–ผ                          โ–ผ                  โ–ผ
             โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”           โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
             โ”‚ Time-Series โ”‚           โ”‚ Aggregates  โ”‚    โ”‚ Alerts      โ”‚
             โ”‚ Store       โ”‚           โ”‚ (Redis/SQL) โ”‚    โ”‚ Engine      โ”‚
             โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜           โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Simple Database Pattern

Low-Volume Metering:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Application โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Usage Table โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Background  โ”‚
โ”‚ (record)    โ”‚     โ”‚ (append)    โ”‚     โ”‚ Aggregator  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                               โ”‚
                                               โ–ผ
                                        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                                        โ”‚ Aggregates  โ”‚
                                        โ”‚ Table       โ”‚
                                        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Quota Enforcement

Quota Check Pattern

public sealed class QuotaEnforcementService(
    IUsageMeteringService metering,
    IEntitlementService entitlements,
    ILogger<QuotaEnforcementService> logger)
{
    public async Task<QuotaCheckResult> CheckQuotaAsync(
        Guid tenantId,
        string metricName,
        decimal requestedQuantity,
        CancellationToken ct = default)
    {
        // Get current usage
        var currentUsage = await metering.GetCurrentUsageAsync(
            tenantId, metricName, ct);

        // Get entitlement limit
        var limit = await entitlements.GetLimitAsync(
            tenantId, metricName, ct);

        if (limit is null)
        {
            return QuotaCheckResult.Unlimited();
        }

        var projectedUsage = currentUsage.TotalQuantity + requestedQuantity;

        if (projectedUsage > limit.HardLimit)
        {
            logger.LogWarning(
                "Quota exceeded for tenant {TenantId}, metric {Metric}",
                tenantId, metricName);

            return QuotaCheckResult.Denied(
                current: currentUsage.TotalQuantity,
                limit: limit.HardLimit,
                requested: requestedQuantity);
        }

        if (projectedUsage > limit.SoftLimit)
        {
            return QuotaCheckResult.Warning(
                current: currentUsage.TotalQuantity,
                softLimit: limit.SoftLimit,
                hardLimit: limit.HardLimit);
        }

        return QuotaCheckResult.Allowed(
            remaining: limit.HardLimit - projectedUsage);
    }
}

Rate Limiting vs Quota

Comparison:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Aspect          โ”‚ Rate Limiting       โ”‚ Quota/Metering     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Time Window     โ”‚ Seconds/minutes     โ”‚ Hours/days/months  โ”‚
โ”‚ Purpose         โ”‚ Protect system      โ”‚ Enforce billing    โ”‚
โ”‚ Enforcement     โ”‚ Hard block          โ”‚ Soft/hard limits   โ”‚
โ”‚ Response        โ”‚ 429 + retry-after   โ”‚ 402/403 + upgrade  โ”‚
โ”‚ Tracking        โ”‚ In-memory/Redis     โ”‚ Durable storage    โ”‚
โ”‚ Precision       โ”‚ Approximate OK      โ”‚ Exact required     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Use BOTH:
- Rate limiting: System protection (per-second/minute)
- Quota: Business limits (per-month billing cycles)

References

Load for detailed implementation:

  • references/event-ingestion.md - High-throughput event capture patterns
  • references/aggregation-strategies.md - Time-series aggregation and rollups

For billing integration patterns, see the billing-integration skill.

Related Skills

  • billing-integration - Payment processing and invoicing
  • entitlements-management - Feature gating and limits
  • subscription-models - Pricing tier definitions

Last Updated: 2025-12-29

Repository

melodic-software
melodic-software
Author
melodic-software/claude-code-plugins/plugins/saas-patterns/skills/usage-metering
3
Stars
0
Forks
Updated1d ago
Added5d ago