Skip to content

DocsOrganizations

End-customer caps

Cap what each of your own customers can spend on AI: one rule for all of them, overrides for specific customers, set from the console or your backend.


INFRO is in private early access. Running these examples requires an early-access account, an active trial or subscription entitlement, and your own provider connection. Self-serve purchasing is not generally open. See Early access.

If you run an AI product, your users (your end customers) each cost you money at your providers. An end-customer cap limits that cost per end customer: for example, every end customer may spend $5 a day, and acme-corp may spend $200 a day. A request that would pass its end customer's cap is refused before it reaches your provider, with an error your app can show to that user.

INFRO identifies an end customer by the id your app sends in metadata.user_id (or the X-INFRO-User-Id header, for requests without a JSON body). It is your own opaque string, up to 128 characters. Use an opaque id, not an email address or a name: it is stored with every request.

A chat request for end customer acme-corp
{
  "model": "anthropic/claude-sonnet-5",
  "messages": [{ "role": "user", "content": "Summarise this ticket" }],
  "metadata": { "user_id": "acme-corp" }
}

Rules and overrides

A rule sets the default for every end customer. There is one rule for the organization and, optionally, one rule for each project. An override changes the amount for one end customer under one rule. INFRO stores only the rules and the overrides you set, never a row per end customer.

SettingValuesMeaning
ScopeOrganization, or one projectAn organization rule counts the end customer's spend in every project. A project rule counts only that project.
AmountUSD per period, or noneThe cap for an end customer with no override. With no amount, only end customers with an override are capped.
Perioddaily, weekly, monthlyDaily resets at 00:00 UTC, weekly at 00:00 UTC on Monday, monthly on your organization's budget cycle day. Always UTC.
Behaviorblock, alertblock refuses a request that does not fit. alert never refuses; the Customers page shows who is over.
Requests without a customer idServe (default), RefuseServe: the request is served and counts against no end-customer cap. Refuse: it is rejected with 400 end_user_id_required.

An override's amount can be a number, 0 (every priced request for that end customer is refused), or null (that end customer is not capped under the rule). The override applies from the next request.

When an organization rule and a project rule both apply, both are enforced independently, like the other budget levels: a request is refused by whichever has no room. A request without a customer id is refused if either rule is set to Refuse. Your key, project, member and organization budgets keep applying on top.

Keep internal traffic with no end customer (batch jobs, evaluations) in a project that has no Refuse rule, or leave the organization rule on Serve and put the strict rule on your product's project.

How a cap is enforced

Before the request is sent to your provider, INFRO checks the end customer's spend for the current period, plus the worst-case cost of that end customer's requests still in flight, against the cap. The request is admitted only if its own worst-case cost fits. This is the same check a blocking budget uses. Every admitted request holds its worst case until it finishes, so concurrent requests cannot pass the cap together.

Spend is INFRO's estimate of what your provider will bill you, from your negotiated rate or the vendor's list rate. A request to a model with no price has no cost to count. Under a blocking end-customer cap it follows your unpriced-traffic setting under Budgets: refused by default (unpriced_model_under_budget), or admitted if you allow unpriced traffic. A rule with no default amount still counts as blocking for this setting.

The refusal

A request over its cap returns 402 with type: "budget_exceeded" and code: "end_user_budget". Beside the usual fields the error carries scope, end_user_id, period and resets_at (ISO 8601, UTC), so your app can tell its user what happened and when they can continue.

402 end_user_budget
{
  "error": {
    "message": "Budget exceeded: end customer `acme-corp` has used its daily spend cap (set by this organization's end-customer rule). It resets at 2026-09-27T00:00:00.000Z, or when the cap is raised under Customers in the console or through the management API.",
    "type": "budget_exceeded",
    "code": "end_user_budget",
    "scope": "end_user",
    "end_user_id": "acme-corp",
    "period": "daily",
    "resets_at": "2026-09-27T00:00:00.000Z"
  }
}
400 end_user_id_required
{
  "error": {
    "message": "This organization requires an end-customer id on every request, and this request sent none. Send your user's id as `metadata.user_id` (or the `X-INFRO-User-Id` header). Use an opaque id, not an email address or a name.",
    "type": "invalid_request_error",
    "code": "end_user_id_required"
  }
}

Do not retry either one unchanged. A 402 end_user_budget clears when the period resets or the cap is raised; a 400 end_user_id_required needs the id in the request.

Set overrides from your backend

When one of your users changes plan, your backend can raise or lower their cap through the management API. Create a management token with only the end_users:write scope: it can manage end-customer caps and nothing else. A token restricted to a project manages that project's rule only.

Set acme-corp's cap under a rule
curl https://api.infro.io/manage/v1/end_user_cap_rules/eucr_01J9Z4/overrides/acme-corp \
  -X PUT \
  -H "Authorization: Bearer $INFRO_MANAGEMENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"amount": "200.00", "note": "Enterprise plan"}'

PUT is idempotent: sending the same body again changes nothing, so a billing webhook can retry it safely. If-Match is optional on overrides; when you send it, the write happens only at that version, and If-Match: "0" means "only if no override exists yet". DELETE on the same path returns the end customer to the rule's default, and succeeds even if there was no override. Percent-encode ids that contain /, ?, # or spaces.

CallScope
GET /end_user_cap_rules, POST /end_user_cap_rules, PATCH and DELETE /end_user_cap_rules/{id}end_users:read / end_users:write
GET /end_user_cap_rules/{rule}/overrides?limit=&starting_after=&q=end_users:read. Ordered by end-customer id; limit up to 200
GET, PUT, DELETE /end_user_cap_rules/{rule}/overrides/{end_user_id}end_users:read / end_users:write

See what each end customer costs

The console's Customers page lists your most expensive end customers for the last 1, 7, 30 or 90 days: estimated cost, requests, cost per request, the change against the previous period, and each cap with how much of it is used. Requests with no price are counted beside the cost, never as $0. Open an end customer to see their daily trend and the projects they used, and to set or remove an override.

A per-customer cost is always an estimate. Provider cost APIs report organization totals, never a figure per end customer, so INFRO can reconcile your total against your provider's bill but cannot reconcile one end customer's share.

Plans

The Customers cost view is on every plan. Creating or tightening a cap needs the Team plan or above. Turning a rule off, deleting it, switching it to Serve and removing an override always work, on any plan.

↑ ↓ navigate↵ open/ anywhere in docs