Skip to content
SaaS4Builders
API Reference

Usage & Meters API

Usage tracking endpoints: recording events with idempotency, viewing usage summaries and meter details, quota enforcement, and cost estimation.

The usage API tracks consumption-based metrics through meters — configurable counters that aggregate usage events over time. Each meter defines how events are aggregated, when counters reset, and whether quota limits are enforced.

Base path: /api/v1/tenant/{tenantId}/...Middleware: auth:sanctumtenant.resolvetenant.memberonboarding.complete


Key Concepts

Meters

A meter is a named counter tied to a feature code. It defines:

PropertyDescription
codeStable identifier (e.g., api-requests, storage-gb)
aggregation_typeHow events are combined: sum, max, count, last_value
reset_intervalWhen the counter resets: monthly, weekly, daily, none
quota_enforcementHow limits are applied: none, soft, hard
unit_labelDisplay label (e.g., "requests", "GB")

Quota Enforcement Levels

LevelBehavior
noneNo enforcement — usage is tracked but never blocked
softUsage is tracked and alerts are generated, but requests are not blocked
hardUsage is blocked with a 429 response when the quota is exceeded

Quota limits are resolved from the tenant's active subscription. Each meter's feature_code is matched against the plan's entitlements to determine the limit. If no matching entitlement exists or the entitlement is boolean, the quota is treated as unlimited.

Aggregation Types

TypeDescription
sumTotal of all event quantities in the period
maxHighest single event quantity in the period
countNumber of events recorded (ignores quantity)
last_valueMost recent event quantity

Get Usage Summary

GET /api/v1/tenant/{tenantId}/usage

Returns a summary of current usage across all active meters for the tenant.

Auth: Bearer token + tenant member + onboarding complete

Response (200):

{
  "data": {
    "tenant_id": "550e8400-e29b-41d4-a716-446655440000",
    "period_start": "2026-03-01T00:00:00.000000Z",
    "period_end": "2026-03-31T23:59:59.000000Z",
    "meters": [
      {
        "meter_id": "660e8400-e29b-41d4-a716-446655440001",
        "meter_code": "api-requests",
        "meter_name": "API Requests",
        "current_usage": 7500.0,
        "quota_limit": 10000,
        "usage_percent": 75.0,
        "aggregation_type": "sum",
        "reset_interval": "monthly",
        "quota_enforcement": "hard",
        "unit_label": "requests"
      },
      {
        "meter_id": "660e8400-e29b-41d4-a716-446655440002",
        "meter_code": "storage-gb",
        "meter_name": "Storage",
        "current_usage": 2.5,
        "quota_limit": null,
        "usage_percent": null,
        "aggregation_type": "last_value",
        "reset_interval": "none",
        "quota_enforcement": "none",
        "unit_label": "GB"
      }
    ]
  }
}
FieldTypeDescription
tenant_idUUIDTenant identifier
period_startdatetimeStart of current usage period
period_enddatetimeEnd of current usage period
metersarrayUsage summary per active meter

Per-meter fields:

FieldTypeDescription
meter_idUUIDMeter identifier
meter_codestringStable meter code
meter_namestringDisplay name
current_usagefloatAggregated usage for the current period
quota_limitint|nullMaximum allowed (null = unlimited)
usage_percentfloat|nullPercentage used (null if unlimited)
aggregation_typeenumsum, max, count, last_value
reset_intervalenummonthly, weekly, daily, none
quota_enforcementenumnone, soft, hard
unit_labelstring|nullUnit display label
The usage period is determined by the meter's reset_interval. Monthly meters align with the subscription's billing period. Weekly and daily meters calculate from the current date. Meters with reset_interval: "none" accumulate indefinitely.

Get Quota Status

GET /api/v1/tenant/{tenantId}/usage/quotas

Returns the quota status for all active meters. This endpoint is designed for building quota warning UI — each meter includes a pre-classified status field.

Auth: Bearer token + tenant member + onboarding complete

Response (200):

{
  "data": {
    "tenant_id": "550e8400-e29b-41d4-a716-446655440000",
    "meters": [
      {
        "meter_id": "660e8400-e29b-41d4-a716-446655440001",
        "meter_code": "api-requests",
        "meter_name": "API Requests",
        "current_usage": 9500.0,
        "quota_limit": 10000,
        "usage_percent": 95.0,
        "status": "warning",
        "quota_enforcement": "hard",
        "unit_label": "requests"
      },
      {
        "meter_id": "660e8400-e29b-41d4-a716-446655440002",
        "meter_code": "storage-gb",
        "meter_name": "Storage",
        "current_usage": 2.5,
        "quota_limit": null,
        "usage_percent": null,
        "status": "ok",
        "quota_enforcement": "none",
        "unit_label": "GB"
      }
    ]
  }
}

Status values:

StatusConditionDescription
okUsage < 80% of quotaNormal usage
warningUsage >= 80% and < 100%Approaching the limit
exceededUsage >= 100% of quotaQuota has been reached or exceeded

Meters with unlimited quotas (quota_limit: null) always report status: "ok".


Get Meter Details

GET /api/v1/tenant/{tenantId}/usage/{meterCode}

Returns detailed usage information for a specific meter, including recent events.

Auth: Bearer token + tenant member + onboarding complete

Path Parameters:

ParamTypeDescription
meterCodestringThe meter's code identifier (e.g., api-requests)

Response (200):

{
  "data": {
    "meter_id": "660e8400-e29b-41d4-a716-446655440001",
    "meter_code": "api-requests",
    "meter_name": "API Requests",
    "current_usage": 7500.0,
    "quota_limit": 10000,
    "usage_percent": 75.0,
    "aggregation_type": "sum",
    "reset_interval": "monthly",
    "quota_enforcement": "hard",
    "unit_label": "requests",
    "period_start": "2026-03-01T00:00:00.000000Z",
    "period_end": "2026-03-31T23:59:59.000000Z",
    "recent_events": [
      {
        "id": "770e8400-e29b-41d4-a716-446655440010",
        "quantity": 500.0,
        "recorded_at": "2026-03-27T14:30:00.000000Z",
        "metadata": {
          "endpoint": "/api/v1/data/export",
          "user_id": "42"
        }
      },
      {
        "id": "770e8400-e29b-41d4-a716-446655440011",
        "quantity": 150.0,
        "recorded_at": "2026-03-27T10:15:00.000000Z",
        "metadata": null
      }
    ]
  }
}

The recent_events array contains up to 20 of the most recent usage events for the current period, sorted by recorded_at descending.

Response (404):

{
  "message": "Meter not found: invalid-code"
}
This endpoint allows viewing inactive meters (for historical data). The usage summary and quota endpoints only include active meters.

Record Usage Event

POST /api/v1/tenant/{tenantId}/usage

Records a new usage event against a meter. Supports idempotent submissions via the idempotency_key field.

Auth: Bearer token + tenant member + onboarding complete

Request Body:

{
  "meter_code": "api-requests",
  "quantity": 500,
  "idempotency_key": "batch-2026-03-27-export-42",
  "recorded_at": "2026-03-27T14:30:00Z",
  "metadata": {
    "endpoint": "/api/v1/data/export",
    "user_id": "42"
  }
}
FieldTypeRequiredDefaultDescription
meter_codestringYesCode of the target meter (max 255 chars)
quantitynumberNo1Usage amount (min: 0)
idempotency_keystringNonullUnique key to prevent duplicate recording (max 255 chars)
recorded_atdatetimeNonowWhen the usage occurred (ISO-8601)
metadataobjectNonullArbitrary key-value data attached to the event

Response (201):

{
  "message": "Usage recorded successfully",
  "data": {
    "id": "770e8400-e29b-41d4-a716-446655440010",
    "meter_code": "api-requests",
    "quantity": 500.0,
    "recorded_at": "2026-03-27T14:30:00.000000Z"
  }
}

Idempotency

The idempotency_key field prevents duplicate event recording. If you submit two requests with the same idempotency_key for the same meter, the second request returns a 409 Conflict:

{
  "message": "Duplicate usage event (idempotency key already exists)"
}

Idempotency is enforced via a database unique constraint on (meter_id, idempotency_key). This is safe against race conditions — even concurrent requests with the same key will result in at most one recorded event.

The idempotency key is scoped to a single meter. The same key can be used across different meters without conflict.

Quota Enforcement

When a meter has quota_enforcement: "hard", the API checks whether recording the event would exceed the quota. If so, the event is rejected with a 429 response:

{
  "message": "Quota exceeded for api-requests: 9500/10000",
  "code": "QUOTA_EXCEEDED"
}

The quota check happens before the event is created — no partial recording occurs.

Meters with soft enforcement record the event and generate alerts but never reject requests. Meters with none enforcement have no quota checking.

Error Responses

StatusCodeDescription
201Usage event recorded successfully
404Meter not found (code does not exist or meter is inactive)
409Duplicate idempotency key for this meter
422Validation error (invalid meter_code, negative quantity, etc.)
429QUOTA_EXCEEDEDHard quota exceeded — event was not recorded

Get Cost Estimate

GET /api/v1/tenant/{tenantId}/usage/cost-estimate

Returns a shadow-calculated estimate of usage costs for the current billing period. This estimate is based on local meter data and unit prices — actual billing is handled by Stripe and may differ due to prorations, taxes, or pricing changes.

Auth: Bearer token + tenant member + onboarding complete

Response (200):

{
  "data": {
    "tenant_id": "550e8400-e29b-41d4-a716-446655440000",
    "period_start": "2026-03-01T00:00:00.000000Z",
    "period_end": "2026-03-31T23:59:59.000000Z",
    "total_amount_cents": 7500,
    "currency": "EUR",
    "is_estimate": true,
    "lines": [
      {
        "meter_code": "api-requests",
        "meter_name": "API Requests",
        "quantity": 7500.0,
        "unit_price_cents": 1,
        "amount_cents": 7500,
        "currency": "EUR",
        "unit_label": "requests"
      }
    ]
  }
}
FieldTypeDescription
total_amount_centsintSum of all line amounts
currencystringCurrency from subscription or tenant preference
is_estimatebooleanAlways true — actual billing may differ
linesarrayPer-meter cost breakdown

Per-line fields:

FieldTypeDescription
meter_codestringMeter identifier
meter_namestringDisplay name
quantityfloatAggregated usage for the period
unit_price_centsintPrice per unit in cents
amount_centsintquantity × unit_price_cents
currencystringISO 4217 currency code
unit_labelstring|nullUnit display label
Only billable meters (those with a linked stripe_price_id) appear in the cost estimate. Meters without Stripe pricing are excluded even if they track usage.

The cost estimate returns an empty lines array if the tenant has no active subscription or no billable meters.


Usage Enums Reference

Aggregation Type

ValueDescription
sumTotal of all event quantities in the period
maxHighest single event quantity
countNumber of events (ignores quantity values)
last_valueMost recent event quantity

Defined in backend/app/Domain/Usage/Enums/AggregationType.php.

Reset Interval

ValueDescription
monthlyResets at the start of each billing month (aligned to subscription period)
weeklyResets every 7 days
dailyResets every 24 hours
noneNever resets — cumulative across all time

Defined in backend/app/Domain/Usage/Enums/ResetInterval.php.

Quota Enforcement

ValueDescription
noneNo enforcement — usage tracked only
softAlerts generated but requests allowed
hardRequests blocked with 429 when quota exceeded

Defined in backend/app/Domain/Usage/Enums/QuotaEnforcement.php.


What's Next