Skip to main content

H-01 Quota service

Brick H of the Enterprise AI Platform. Technology: Developed quota service + Redis (atomic reservations with Lua scripts and TTL) + PostgreSQL (definitions, committed usage). French version: ../../fr/bricks/H-01-quota-service.md.

Role and responsibilities​

  • Enforce limits per tenant, project, environment, agent, user, model or usage type, on cost, tokens, requests, GPU-seconds, media seconds or sessions.
  • Reserve the estimated cost before execution so parallel calls cannot overshoot, commit the measured amount, release on failure.

Position and interactions​

  • Execution path: synchronous on the hot path (reserve before execution, commit after).
  • Gateway, Tool Gateway, media workers and realtime sessions call reserve; the Go middleware gatewaymw and Python context manager wrap the cycle.
  • Expired reservations are swept and released; crossing 50%, 80% and 100% of a limit raises an alert (G-08); the thresholds are set by EAI_FINOPS_ALERT_THRESHOLDS (comma separated percentages, default 50,80,100, a malformed value is an error, never ignored).

Open source and developed parts​

  • Open source reused: Redis (or Valkey), PostgreSQL.
  • Developed in the platform: Whole service: hierarchical scopes, reservation protocol, period rollover, actions block/warn/degrade (route to cheaper model).

Data model​

Control Plane tables (schema eai): eai.quotas, eai.quota_reservations, eai.usage_events.

Every tenant table has tenant_id uuid NOT NULL and a row level security policy on current_setting('app.tenant_id', true)::uuid. ClickHouse usage_events is the analytics replica of eai.usage_events (ReplacingMergeTree keyed by event_id).

Contract: contracts/v1/usage-event.schema.json (schema_version 1.0). Usage is always reported with contracts/v1/usage-event.schema.json, audit with contracts/v1/audit-event.schema.json.

Estimated, measured and billed cost​

Every Usage Event carries three amounts in cost (Usage Event contract, cost.estimated, cost.measured, cost.billed, each a decimal string with a currency or null):

AmountWhenSourceUsed for
Estimated costBefore executionPricing engine (H-03) applied to predicted units (prompt tokens counted, max output tokens, image count, video seconds)Budget control, quota reservation (H-01), approval thresholds
Measured costAfter executionPricing engine applied to units really consumed, as reported by the provider response, LiteLLM callback, worker or GPU samplerShowback, quotas commit, dashboards
Billed costAfter reconciliationProvider invoices and exports imported in /v1/finops/reconciliation/importsFinance, variance analysis, chargeback

Rules applied by the pricing engine and the ledger:

  • No double counting of reasoning tokens. pricing.reasoning_billing records how the provider bills them: included_in_output (reasoning tokens are already inside output_tokens, they are stored in units.reasoning_tokens for analysis only and are not priced again), separate (priced with their own rate), not_billed, not_exposed.
  • Cached tokens. cached_input_tokens, cache_read_tokens and cache_write_tokens are priced with their own rates of the price card; cached input tokens are subtracted from full price input tokens when the provider reports them inside input_tokens.
  • Price applicable at call date. The price card is the one whose effective period contains occurred_at; pricing.pricing_version, pricing.price_card_id and pricing.effective_at are frozen in the event, so a later price change never rewrites history.
  • Currency and FX. Amounts keep the provider currency; conversion to the tenant reporting currency uses eai.fx_rates of the call date (GET/PUT /v1/finops/fx-rates), and the rate used is stored for audit.
  • Local model allocation policy. Local models (vLLM, Diffusers, faster-whisper, pyannote, Piper, video GPU workers) have no invoice: their cost comes from an allocation policy (eai.allocation_policies) combining GPU hours, infrastructure overhead, estimated energy, storage and real usage (GPU-seconds from DCGM samples, tokens served), referenced by pricing.allocation_policy_id.
  • Event fields. Each event records the tariff applicable at call date, currency, provider, model, region, tenant, agent, session and task (pricing, provider, model_id, region, tenant_id, agent_id, session_id, task_id), plus project, environment, run, request, provider request and trace ids for correlation.
  • LiteLLM is a usage source, not the only accounting source. LiteLLM callbacks cover LLM, embeddings and some media calls through providers. Local GPU inference, media workers, tool calls, connectors, sandbox executions and asynchronous operations (batch jobs, video generation) emit their own Usage Events directly to /v1/finops/usage or /v1/finops/usage/batch.

API endpoints​

MethodPath
POST/v1/quotas
POST/v1/quotas/reserve
POST/v1/quotas/{id}/commit
POST/v1/quotas/{id}/release
GET/v1/quotas/usage
GET/v1/finops/alerts

POST /v1/quotas​

Request:

{
"name": "invoices-agt-ap-monthly",
"scope_type": "agent",
"scope_value": "agt_ap",
"period": "month",
"enforcement": "hard",
"limit_cost": "500.00",
"currency": "EUR",
"enabled": true
}

Response:

{
"id": "quo_4",
"status": "active"
}

POST /v1/quotas/reserve​

Request:

{
"scope": {
"agent_id": "agt_ap"
},
"cost": "1.80",
"tokens": 14000,
"ttl_seconds": 600
}

Response:

{
"reservation_id": "qres_51",
"quota_id": "quo_4",
"remaining": {
"amount": "212.40",
"currency": "EUR"
},
"expires_at": "2026-10-29T10:10:00Z"
}

POST /v1/quotas/{id}/commit​

Request:

{
"cost": "1.62",
"tokens": 13250
}

Response:

{
"reservation_id": "qres_51",
"status": "committed",
"released": {
"amount": "0.18",
"currency": "EUR"
}
}

POST /v1/quotas/{id}/release​

No request body.

Response:

{
"reservation_id": "qres_51",
"status": "released"
}

GET /v1/quotas/usage​

Response:

{
"items": [
{
"quota_id": "quo_4",
"used": "287.60",
"reserved": "1.80",
"limit": "500.00",
"currency": "EUR",
"ratio": 0.579
}
]
}

GET /v1/finops/alerts​

Response:

{
"items": [
{
"quota_id": "quo_4",
"threshold": 0.8,
"triggered_at": "2026-10-27T08:00:00Z"
}
]
}

SDK examples​

Python​

from enterprise_ai import EnterpriseAI

client = EnterpriseAI(base_url="<control-plane-url>", api_key="eai_live_xxx", tenant="acme", locale="en")

with client.quotas.reservation(cost="1.80", scope={"agent": "agt_ap"}) as r:
result = client.gateway.video(model="video-gen-gpu", prompt="Teaser", seconds=15)
r.commit(cost="1.62") # released automatically on exception
estimate = client.finops.estimate("openai", "gpt-4o-mini", input_tokens=12000, output_tokens=2000)
client.quotas.create(name="monthly-cost", scope_type="tenant", period="month", limit_cost="500.00", currency="EUR")
client.quotas.reserve(cost="1.80", scope={"agent": "agt_ap"})
client.quotas.commit("qres_51", cost="1.62")
client.quotas.release("qres_51")
client.quotas.usage()
client.finops.alerts()

TypeScript​

import { EnterpriseAI } from "@enterprise-ai/sdk";

const client = new EnterpriseAI({ baseUrl: "<control-plane-url>", apiKey: process.env.EAI_API_KEY, tenant: "acme", locale: "en" });
await client.quotas.create({ name: "monthly-cost", scope_type: "tenant", period: "month", limit_cost: "500.00", currency: "EUR" });
await client.quotas.reserve({ cost: "1.80", scope: { agent: "agt_ap" } });
await client.quotas.commit("qres_51", { cost: "1.62" });
await client.quotas.release("qres_51");
await client.quotas.usage();
await client.finops.alerts.list();

Java​

EnterpriseAIClient client = EnterpriseAIClient.builder()
.baseUrl("<control-plane-url>")
.apiKey(System.getenv("EAI_API_KEY"))
.tenant("acme")
.locale(Locale.ENGLISH)
.build();
client.quotas().create(Map.of("name", "monthly-cost", "scope_type", "agent", "scope_value", "agt_ap", "period", "month", "limit_cost", 500.00, "currency", "EUR"));
client.quotas().reserve(Map.of("agent_id", "agt_ap", "amount", "1.80", "currency", "EUR"));
client.quotas().commit("qres_51", Map.of("cost", 1.62));
client.quotas().release("qres_51");
client.quotas().usage();
client.finops().alerts();

Go​

client, err := eai.NewClient(eai.WithBaseURL("<control-plane-url>"), eai.WithAPIKey(os.Getenv("EAI_API_KEY")), eai.WithTenant("acme"), eai.WithLocale("en"))
if err != nil { log.Fatal(err) }
ctx := context.Background()
_, err = client.Quotas.Create(ctx, map[string]any{"name": "ap-monthly", "scope_type": "agent", "scope_value": "agt_ap", "period": "month", "limit_cost": "500.00", "currency": "EUR"})
_, err = client.Quotas.Reserve(ctx, map[string]any{"scope": map[string]any{"agent_id": "agt_ap"}, "cost": "1.80"})
_, err = client.Quotas.Commit(ctx, "qres_51", map[string]any{"cost": "1.62"})
_, err = client.Quotas.Release(ctx, "qres_51", nil)
_, err = client.Quotas.Usage(ctx, nil)
_, err = client.FinOps.Alerts(ctx, nil)

// usage package: emit a Usage Event from a local worker
emitter := usage.NewEmitter(client, usage.Options{})
defer emitter.Close(ctx)
ev, err := usage.NewEvent("acme", "gpu_local", "worker").Model("local", "llama-70b-local", "", "").Unit("gpu_seconds", 12.4).Unit("energy_wh", 1.7).Build()
if err == nil { err = emitter.Emit(ev) }
// gatewaymw: reserve, call, commit around any http.Handler
http.Handle("/llm", gatewaymw.New(gatewaymw.Config{Client: client, Action: func(r *http.Request) string { return "llm.invoke" }, Reserve: func(r *http.Request) map[string]any { return map[string]any{"cost": "0.50", "tokens": 4000} }})(llmHandler))

Configuration​

Environment variables: EAI_DATABASE_URL.

Helm values keys (deploy/helm/enterprise-ai/values.yaml): redis.enabled.

Security​

  • Only tenant_admin and finops create or change quotas; agents can only reserve within their scope.
  • Tenant resolved from the credential, never from the body; PostgreSQL row level security on every tenant table.
  • Every mutation and policy decision writes a hash chained audit event (G-11).
  • Errors use the standard envelope with message_i18n en and fr.

Observability​

  • eai_quota_reservations_active, rejections by reason, reservation to commit ratio.
  • W3C traceparent propagated, X-Request-Id returned, spans carry eai.tenant_id, eai.project, eai.agent_id, eai.run_id.

FinOps units​

  • Quotas consume the estimated cost at reserve time and the measured cost at commit time; billed cost never changes a past quota decision.

Failure modes and resilience​

  • Counters live in PostgreSQL (eai.quota_counters, eai.quota_reservations), the system of record; a Redis adapter may implement the same QuotaStore protocol. If PostgreSQL is unavailable no reservation is granted and the call is refused.
  • Client crash after reserve -> TTL expiry releases the amount.

Standalone adoption​

A client of a client can use only quotas to protect its own LLM gateway (Go gatewaymw).

Acceptance tests​

  1. 100 parallel reservations against a 10 EUR quota never exceed 10 EUR reserved.
  2. Commit lower than reservation releases the difference.
  3. quota_exceeded error contains message_i18n en and fr.

Reservation on every metered resource​

On top of the Model Gateway chat, stream, embeddings and rerank, every metered resource reserves its worst case before the outbound call (eai.domains.finops.reservation): tools, MCP tools/call, specialized adapters (/v1/gateway/audio/*, /v1/gateway/images/*), multimodal and media jobs, realtime WebSocket sessions, internal embeddings of the RAG (ingest and query) and of the memory (write and search).

  • The estimate comes from the H03 price cards (finops.estimate). Without a matching card the reference rate of the resource is used (multimodal rates, declared tool price, maximum session duration for realtime).
  • An exceeded hard quota returns 429 quota_exceeded before any outbound call. A realtime session is then closed with code 4429.
  • The reservation is committed with the measured amount of the Usage Event, or released when the call fails (upstream error, failed or cancelled job, disconnection without session.end).
  • A reservation left open (long queued job) is expired by the existing quota_expiry job.
  • The memory now emits an embeddings Usage Event (labels feature=memory, operation=write|search).

Implementation roadmap