Skip to content

Usage Metering Administration

Usage metering tracks request counts, token consumption, and cost estimates per organisation across billing periods. Metering runs at two layers: a real-time counter for quota enforcement, and background aggregation rollups for reporting.


Every authenticated request increments the organisation’s counter in OrgUsageCounter. The counter is keyed to a calendar month billing period and resets automatically at the start of each new month.

A Redis hot path (sub-millisecond) handles the increment for active traffic. PostgreSQL remains the authoritative record; a background sync task writes Redis counters to Postgres every 60 seconds. If Redis is unavailable, all operations fall back to Postgres transparently.

A background aggregation scheduler materialises hourly, daily, and monthly rollups into the org_usage_rollups table from raw message-level data. The scheduler interval is controlled by USAGE_AGGREGATION_INTERVAL_MINUTES (default: 60 minutes).

Message.conversation_id
→ Conversation.user_id
→ User.tenant_id (= org_id)
→ OrgUsageRollup (hourly | daily | monthly)

Each rollup row records request_count, input_tokens, output_tokens, cost_estimate, and a model_breakdown JSONB dict keyed by model_id.


Monthly request limits are set per plan tier and locked by PO decision.

Plan tier Monthly limit Notes
devfree_saas 100,000 Default for free-tier organisations
devpro_saas 1,000,000
team_saas 1,000,000
enterprise_saas 1,000,000 Overridable via enterprise_entitlements.custom_request_limit
enterprise_outpost 1,000,000 Overridable via enterprise_entitlements.custom_request_limit

Legacy tier keys free (100,000) and paid (1,000,000) are supported for backward compatibility until migration 045 runs.

Enterprise custom limits are stored in enterprise_entitlements.custom_request_limit. When set, the custom value takes precedence over the tier default.


All endpoints require UserRole.ADMIN and are tenant-scoped via the caller’s tenant_id.

GET /api/v1/admin/usage/summary
Authorization: Bearer <token>

Returns the current billing period’s request count, token totals, cost estimate, plan limit, and warning level.

Response

{
"org_id": "b3e2a1c0-...",
"plan_tier": "enterprise_saas",
"request_count": 834200,
"input_tokens": 12400000,
"output_tokens": 3100000,
"cost_estimate": 47.83,
"limit": 1000000,
"period_start": "2026-03-01",
"period_end": "2026-03-31",
"percentage_used": 83.4,
"warning_level": "warning_80"
}
Field Type Description
plan_tier string Canonical tier key
request_count int Requests in the current billing period
input_tokens int Total input tokens (from monthly rollup)
output_tokens int Total output tokens (from monthly rollup)
cost_estimate float Estimated USD cost for the period
limit int Monthly request limit
percentage_used float Requests as percentage of limit (0–100+)
warning_level string "none", "warning_80", or "warning_95"
GET /api/v1/admin/usage/history
?granularity=daily
&limit=30
&cursor=2026-03-10T00:00:00+00:00
Authorization: Bearer <token>

Returns cursor-paginated rollup history. Ordered newest-first (period_start DESC).

Query parameters

Parameter Default Description
granularity daily hourly, daily, or monthly
limit 30 Items per page (1–90)
cursor ISO 8601 period_start of the oldest item on the previous page

Response

{
"items": [
{
"period_start": "2026-03-11T00:00:00+00:00",
"period_end": "2026-03-12T00:00:00+00:00",
"request_count": 38400,
"input_tokens": 560000,
"output_tokens": 140000,
"cost_estimate": 2.18
}
],
"next_cursor": "2026-03-10T00:00:00+00:00",
"total": 1
}

Pass next_cursor from a response as cursor on the next request to page backward through history.

GET /api/v1/admin/usage/by-model
?granularity=monthly
&period_start=2026-03-01
&period_end=2026-03-31
Authorization: Bearer <token>

Aggregates the model_breakdown JSONB from rollup rows for the requested period and returns one item per unique model.

Query parameters

Parameter Default Description
granularity monthly hourly, daily, or monthly
period_start Inclusive start date (YYYY-MM-DD)
period_end Inclusive end date (YYYY-MM-DD)

Response

{
"items": [
{
"model_id": "claude-sonnet-4-6",
"provider": "anthropic",
"request_count": 450000,
"input_tokens": 6800000,
"output_tokens": 1700000,
"cost_estimate": 29.40
},
{
"model_id": "gpt-4o",
"provider": "openai",
"request_count": 384200,
"input_tokens": 5600000,
"output_tokens": 1400000,
"cost_estimate": 18.43
}
]
}

Items are sorted alphabetically by model_id.

GET /api/v1/admin/usage/alerts?limit=20&offset=0
Authorization: Bearer <token>

Returns paginated alert records for the caller’s organisation, ordered newest-first by triggered_at.

Query parameters

Parameter Default Description
limit 20 Records per page (1–100)
offset 0 Records to skip

Response

{
"items": [
{
"id": "a4f2c1e0-...",
"org_id": "b3e2a1c0-...",
"alert_type": "warning_80",
"message": "Usage at 80% threshold: 834,200 / 1,000,000 requests (83.4%)",
"created_at": "2026-03-10T14:22:00Z"
}
],
"total": 3
}
GET /api/orgs/{org_id}/usage
Authorization: Bearer <token>

Accessible to platform admins (any org_id) and org admins (own tenant_id only). Returns the same fields as the summary endpoint.


UsageThrottleMiddleware reads the organisation’s current usage percentage on each authenticated, non-M2M HTTP request and applies one of three tier actions.

Threshold (env var) Default Effect
USAGE_THROTTLE_WARN 80% X-Usage-Warning: threshold=80,percentage=<pct> header added to responses
USAGE_THROTTLE_SLOW 95% Above header + X-Usage-Throttle: active — rate limit middleware applies 50% RPM reduction
USAGE_THROTTLE_BLOCK 100% HTTP 429 returned immediately

Usage percentages are cached per org. Configure the TTL with USAGE_THROTTLE_CACHE_TTL (default: 300 seconds).

Bypasses:

  • /health path — always exempt
  • Unauthenticated requests — no org to check
  • M2M tokens (token_type=m2m JWT claim) — M2M clients have separate quota enforcement

Fail-open: if the DB/cache lookup fails for any reason the request is passed through without blocking.

When percentage_used >= 100:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: <seconds until end of billing period>
{
"detail": "Monthly usage limit reached",
"upgrade_url": "/billing/upgrade",
"retry_after_seconds": 1728000
}

retry_after_seconds is calculated as the number of seconds from now until UTC midnight on the first day of next month.


UsageAlertService fires an alert record when an organisation crosses a configured threshold percentage for the first time in a billing period.

Default thresholds: 50%, 80%, 95%, 100%

Override via environment variable (comma-separated integers):

USAGE_ALERT_THRESHOLDS=50,80,95,100

De-duplication: At most one alert fires per (org_id, threshold_pct) per billing period. The period scope is determined by period_start — a new billing month resets the de-duplication state.

Each alert stores:

Field Description
threshold_pct Threshold that was crossed (50/80/95/100)
current_pct Actual usage percentage at trigger time
request_count Request count at trigger time
limit Plan limit at trigger time
triggered_at UTC timestamp of the event
webhook_delivered Whether the webhook callback succeeded
webhook_error Last delivery error (if failed)

For each fired alert, the service looks for enabled webhooks on the organisation that subscribe to the usage.threshold event. If found, the following payload is delivered:

{
"event": "usage.threshold",
"org_id": "b3e2a1c0-...",
"threshold_pct": 80,
"current_pct": 83.4,
"request_count": 834200,
"limit": 1000000,
"triggered_at": "2026-03-10T14:22:00+00:00"
}

Webhook delivery failure does not suppress the alert record — the row is always persisted. Retry is not automatic; check webhook_error for delivery failure details.

Configure webhooks at Settings → Webhooks in the admin portal or via the webhooks API.


Aggregation runs on a background asyncio loop. Configure with:

Env var Default Description
USAGE_AGGREGATION_INTERVAL_MINUTES 60 Minutes between aggregation runs

The scheduler fires for the previous completed period to avoid aggregating partial windows. Aggregation is idempotent — re-running for the same (org_id, period_type, period_start) performs an upsert.

The scheduler also fires usage threshold alerts after each successful aggregation run. Alert checks use the monthly plan limit for the percentage calculation.



Per-User and Per-Group Budget Configuration

Section titled “Per-User and Per-Group Budget Configuration”

In addition to org-level plan limits, the gateway enforces per-user and per-group budget caps. Budget enforcement runs in the payload analysis stage — a request blocked by a budget cap does not consume provider tokens and does not produce a DLP finding.

Each quota configuration supports six independent limits. Set any combination; a null value means unlimited for that dimension.

Dimension Field Scope Description
Daily tokens daily_token_limit Per calendar day (UTC) Maximum input_tokens + output_tokens per day
Monthly tokens monthly_token_limit Per calendar month (UTC) Maximum tokens per month
Daily requests daily_request_limit Per calendar day (UTC) Maximum request count per day
Monthly requests monthly_request_limit Per calendar month (UTC) Maximum request count per month
Daily cost daily_cost_limit_usd Per calendar day (UTC) Maximum spend in USD per day
Monthly cost monthly_cost_limit_usd Per calendar month (UTC) Maximum spend in USD per month

All time boundaries are UTC. Daily limits reset at 00:00:00 UTC. Monthly limits reset on the first of each month.

Terminal window
# Set quota
PUT https://api.arbitex.ai/api/v1/admin/users/{user_id}/quota
Authorization: Bearer arb_live_your-api-key-here
Content-Type: application/json
{
"daily_token_limit": 100000,
"monthly_cost_limit_usd": 50.00
}
# Read current quota
GET https://api.arbitex.ai/api/v1/admin/users/{user_id}/quota
# Remove all caps
DELETE https://api.arbitex.ai/api/v1/admin/users/{user_id}/quota

Group quota limits apply to the aggregate usage of all members — not per-member.

Terminal window
PUT https://api.arbitex.ai/api/v1/admin/groups/{group_id}/quota
Content-Type: application/json
{
"monthly_token_limit": 5000000,
"monthly_cost_limit_usd": 500.00,
"daily_request_limit": 1000
}
GET https://api.arbitex.ai/api/v1/admin/groups/{group_id}/quota
DELETE https://api.arbitex.ai/api/v1/admin/groups/{group_id}/quota
  1. Load the user’s per-user quota (if any)
  2. Load per-group quotas for every group the user belongs to
  3. Compute the effective limit for each dimension — the minimum across user-level and all applicable group-level limits
  4. For group quotas, check the aggregate group usage (total across all group members) against the group ceiling
  5. If any dimension is exceeded, block the request immediately

The effective limit is always the most restrictive value. If a user’s monthly cost cap is $100 and their group cap is $500, the user’s $100 cap applies. If the group’s aggregate usage reaches $500, the group cap blocks further requests from any member even if individuals have not reached their personal cap.

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: <reset timestamp>
{
"error": "quota_exceeded",
"quota_type": "monthly_cost_usd",
"limit": 50.0,
"used": 50.12,
"reset_at": "2026-04-01T00:00:00+00:00"
}

quota_type values: daily_tokens, monthly_tokens, daily_requests, monthly_requests, daily_cost_usd, monthly_cost_usd.

Field Value
action_taken BLOCK
match_reason quota_exceeded
stage_latencies.quota_check_ms Time spent on the quota check
stage_latencies.policy_eval_ms 0.0 (policy evaluation did not run)
stage_latencies.provider_ms 0.0 (no provider call was made)

Budget denials also trigger a quota_exceeded webhook event. The webhook payload includes the user ID, group ID (if the group cap was the limiting factor), quota type, limit value, and current usage.


Assign models to capability tiers and configure optimization strategies for budget-aware model selection.

Terminal window
POST https://api.arbitex.ai/api/v1/admin/cost-routing/tiers/assign
Content-Type: application/json
{
"model_id": "gpt-4o-mini",
"provider": "openai",
"tier": "standard",
"input_cost_per_1k": 0.15,
"output_cost_per_1k": 0.60
}
GET https://api.arbitex.ai/api/v1/admin/cost-routing/tiers
Strategy Behavior
cheapest_combined Lowest combined input + output cost per 1K tokens (default)
cheapest_input Minimize input cost per 1K tokens
cheapest_output Minimize output cost per 1K tokens
weighted Weighted random selection proportional to each model’s configured weight
lowest_latency Select the model with the lowest observed latency
balanced Weighted combination of cost and latency

Preview the selection for a tier:

Terminal window
POST https://api.arbitex.ai/api/v1/admin/cost-routing/select
Content-Type: application/json
{
"tier": "standard",
"optimize": "cheapest_combined"
}

When a Policy Engine ROUTE_TO rule specifies a tier, the gateway selects the cheapest available model in that tier for the request’s provider.