iblai-api-spend-caps
Configure and read LLM spend caps for an organization. A spend cap is an
admin-imposed maximum LLM cost enforced at one of three scopes:
tenant — the whole org (one cap per organization).
- agent — one agent (one cap per agent).
- user_agent — one user on one agent (an explicit per-user cap).
Each cap has a rolling interval (day/week/month/year, calendar
aligned), a dollar limit (max_cost_usd), an enforcement mode
(block refuses further chats once exceeded; alert_only only notifies), and
alert thresholds (fractions of the limit that fire a near-limit alert). When
a chat is blocked, the request is refused with HTTP 429 and a body naming the
interval that was hit (dollar amounts are deliberately omitted). Spend counters
are reconciled from ClickHouse on a schedule and surfaced read-only so a UI
can render a progress bar.
Auth & conventions
- Base URL:
https://api.iblai.app/dm — these are Data Manager (DM)
endpoints, so the /dm prefix is required; the /api/ai-mentor/... paths
below are appended to it (e.g.
https://api.iblai.app/dm/api/ai-mentor/orgs/$IBLAI_ORG/spend-caps/tenant/).
Omitting /dm will not resolve.
- Header:
Authorization: Api-Token $IBLAI_API_KEY on every request.
- Path vars:
{org} = $IBLAI_ORG (the org key). {mentor} = an agent's
unique_id (a UUID). {user_id} / {username} = the target user.
- Endpoint prefix twin: every path under
api/ai-mentor/ also resolves
under api/ai-agent/ (alias); either works, pick one and be consistent.
- Permission tiers: all cap configuration endpoints (organization / agent /
user-agent read+write) require a platform admin (RBAC action prefix
Ibl.Mentor/SpendCaps/*). The status endpoint is safe to show end users: any
authenticated member may read their own status, and admins may read any
user's — it returns only a coarse zone + percent, never raw dollars.
- PUT is upsert: writing a cap that doesn't exist yet returns 201
Created; writing over an existing one returns 200 OK.
- DELETE and PUT (destructive / budget-changing) calls say "Confirm with the
user first."
- Not connected yet? Run
/iblai-api-login first to populate IBLAI_ORG,
IBLAI_USERNAME, and IBLAI_API_KEY.
Concepts
- scope —
tenant | agent | user_agent. Set by the endpoint you call,
never sent in the body. Narrower scopes carry mentor_unique_id (agent,
user_agent) and username (user_agent).
- interval_type —
day | week | month | year. The rolling window is
calendar aligned (day = midnight, week = Monday, month = 1st, year = Jan 1).
- enforcement —
block (default): once current_spend_usd >= max_cost_usd
the next chat is refused with 429. alert_only: never blocks; only drives
near-limit alerts.
- alert_thresholds — a list of fractions in
(0, 1] (default [0.8, 0.95]
= 80% and 95%). Each fires an admin alert once per period.
- Read-only counters —
current_spend_usd, remaining_usd, is_exceeded,
period_started_at, last_reconciled_at are reconciled from ClickHouse and
ignored if sent in a write body.
- Applicability — for a given chat, the organization cap always applies; the agent
cap applies when an agent is in play; the user's own per-agent cap applies when
both agent and user are known. When several hard-block caps are exceeded, the
most specific one (user > agent > organization) is reported.
Reads
Organization cap
- GET
/api/ai-mentor/orgs/{org}/spend-caps/tenant/ — the org-wide cap, or
404 if none is set. Returns the full cap object (see Schema).
Agent caps
- GET
/api/ai-mentor/orgs/{org}/spend-caps/agents/[?mentor={mentor}] — list
agent-scoped caps for the org; pass mentor to filter to one agent. Returns a
list of cap objects.
- GET
/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-cap/ — the single
cap for one agent, or 404 if none. The agent is resolved org-scoped, so
an agent from another org returns 404.
User-per-agent caps
- GET
/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-caps/users/[?username=&email=]
— list explicit per-user caps on one agent; optional username / email
filters.
- GET
/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-caps/users/{username}/
— the one cap for a specific user on that agent, or 404.
User spend status (safe to show end users)
GET /api/ai-mentor/orgs/{org}/spend-caps/status/{user_id}/[?mentor={mentor}]
— the coarse status across every cap that applies to {user_id} (organization cap
always; agent + that user's own per-agent cap when mentor is given). Any
member may read their own; admins may read anyone's. Returns only a zone
and percentage — never raw spend/limit dollars — so it is safe to surface to
end users. Returns 400 if mentor is not a valid UUID.
{
"status": "warning",
"blocked": false,
"caps": [
{
"scope": "tenant",
"interval_type": "month",
"enforcement": "block",
"status": "warning",
"percent_used": 82.5,
"warning_threshold": 0.8,
"mentor_unique_id": null,
"username": null
}
]
}
status is the worst zone across the applicable caps (ok → warning →
exceeded); blocked is true when a hard-block cap is already exceeded (the
next chat would be refused).
Writes (platform admin — confirm with the user first)
All writes accept the same body fields: max_cost_usd (required on create,
0), interval_type (required), enforcement (default block),
alert_thresholds (default [0.8, 0.95]), enabled (default true). scope,
the agent, and the user are inferred from the URL — never the body. Counter
fields sent in the body are ignored.
Organization cap
- PUT
/api/ai-mentor/orgs/{org}/spend-caps/tenant/ — create (201) or update
(200) the org-wide cap.
- DELETE
/api/ai-mentor/orgs/{org}/spend-caps/tenant/ — remove it (204).
Agent cap
- PUT
/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-cap/ — create/update
the cap for one agent.
- DELETE
/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-cap/ — remove it.
User-per-agent cap
- PUT
/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-caps/users/{username}/
— create/update the cap for one user on one agent (username comes from the URL,
not the body).
- DELETE
/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-caps/users/{username}/
— remove it.
Example
dm=https://api.iblai.app/dm
auth="Authorization: Api-Token $IBLAI_API_KEY"
# Set a $200/month hard-block tenant cap that alerts at 75% and 90%.
curl -X PUT "$dm/api/ai-mentor/orgs/$IBLAI_ORG/spend-caps/tenant/" \
-H "$auth" -H 'Content-Type: application/json' \
-d '{"max_cost_usd": "200.00", "interval_type": "month",
"enforcement": "block", "alert_thresholds": [0.75, 0.9]}'
# Cap one agent at $50/week, alert-only (never blocks chats).
curl -X PUT "$dm/api/ai-mentor/orgs/$IBLAI_ORG/mentors/$MENTOR/spend-cap/" \
-H "$auth" -H 'Content-Type: application/json' \
-d '{"max_cost_usd": "50.00", "interval_type": "week", "enforcement": "alert_only"}'
# How close is a user to their limits? (learner-safe, dollars hidden)
curl "$dm/api/ai-mentor/orgs/$IBLAI_ORG/spend-caps/status/$IBLAI_USERNAME/?mentor=$MENTOR" \
-H "$auth"
# Remove the tenant cap.
curl -X DELETE "$dm/api/ai-mentor/orgs/$IBLAI_ORG/spend-caps/tenant/" -H "$auth"
Notes
- Blocking is 429, not 402. A spend cap that blocks a chat returns HTTP
429 with
{"error_code": "spend_cap_exceeded", ...}; this is distinct from
the 402 returned when a user runs out of purchased credits (see
iblai-api-billing). A cap ceilings admin-set LLM cost; credits are a per-user
prepaid balance.
- Counters are eventually consistent.
current_spend_usd is refreshed by a
scheduled reconcile task from ClickHouse, so it can lag real-time spend
slightly. Enforcement reads the cached value to stay off the hot path.
- Status hides dollars by design. The status endpoint only exposes
percent_used and a zone so a non-admin cannot read org- or agent-wide spend
totals; use the admin read endpoints for the dollar figures.
- One cap per (scope, agent, user). Unique constraints allow a single organization
cap, one cap per agent, and one cap per (agent, user); PUT upserts that single
row.
- Disable vs delete. Set
enabled: false to pause a cap without losing its
configuration; DELETE removes it entirely.
Schema
Cap object (read; write accepts the non-read-only fields):
| Field |
Type |
Notes |
id |
int |
read-only |
scope |
str |
tenant | agent | user_agent; read-only (from URL) |
platform_key |
str |
read-only |
mentor_unique_id |
str | null |
read-only; set for agent/user_agent |
mentor_name |
str | null |
read-only |
username |
str | null |
read-only; set for user_agent |
email |
str | null |
read-only; set for user_agent |
interval_type |
str |
write, required: day|week|month|year |
max_cost_usd |
decimal string |
write, required on create, > 0 |
enforcement |
str |
write: block (default) | alert_only |
alert_thresholds |
list[float] |
write: fractions in (0, 1]; default [0.8, 0.95] |
enabled |
bool |
write: default true |
period_started_at |
datetime | null |
read-only (reconciled) |
current_spend_usd |
decimal string |
read-only (reconciled) |
remaining_usd |
decimal string |
read-only (max_cost_usd - current_spend_usd, floored at 0) |
is_exceeded |
bool |
read-only (current_spend_usd >= max_cost_usd) |
last_reconciled_at |
datetime | null |
read-only |
created_at / updated_at |
datetime |
read-only |
Status summary (from the status endpoint):
| Field |
Type |
Notes |
status |
str |
worst zone: ok | warning | exceeded |
blocked |
bool |
a hard-block cap is already exceeded |
caps[] |
list |
one entry per applicable cap |
caps[].scope |
str |
tenant | agent | user_agent |
caps[].interval_type |
str |
day | week | month | year |
caps[].enforcement |
str |
block | alert_only |
caps[].status |
str |
ok | warning | exceeded |
caps[].percent_used |
float |
0–100+, one decimal |
caps[].warning_threshold |
float | null |
lowest configured alert threshold |
caps[].mentor_unique_id |
str | null |
set for agent/user_agent caps |
caps[].username |
str | null |
set for user_agent caps |
1---2name: iblai-api-spend-caps3description: Manage an ibl.ai organization's LLM spend caps via the platform API — admin-imposed maximum LLM cost at three scopes (organization-wide, one agent, or one user on one agent), each with a rolling interval (day/week/month/year), a hard-block or alert-only enforcement mode, and near-limit alert thresholds. Read the current fill/zone for a user's applicable caps (safe to show end users, dollars hidden). Use when setting a budget ceiling on an org/agent/user, listing configured caps, checking how close a user is to their limit, or removing a cap. Spend counters are reconciled from ClickHouse and are read-only.4---56# iblai-api-spend-caps78Configure and read **LLM spend caps** for an organization. A spend cap is an9admin-imposed maximum LLM cost enforced at one of three scopes:1011- `tenant` — the whole org (one cap per organization).12- **agent** — one agent (one cap per agent).13- **user_agent** — one user on one agent (an explicit per-user cap).1415Each cap has a rolling **interval** (`day`/`week`/`month`/`year`, calendar16aligned), a dollar limit (`max_cost_usd`), an **enforcement** mode17(`block` refuses further chats once exceeded; `alert_only` only notifies), and18**alert thresholds** (fractions of the limit that fire a near-limit alert). When19a chat is blocked, the request is refused with **HTTP 429** and a body naming the20interval that was hit (dollar amounts are deliberately omitted). Spend counters21are reconciled from ClickHouse on a schedule and surfaced **read-only** so a UI22can render a progress bar.2324## Auth & conventions2526- **Base URL:** `https://api.iblai.app/dm` — these are Data Manager (DM)27 endpoints, so the **`/dm` prefix is required**; the `/api/ai-mentor/...` paths28 below are appended to it (e.g.29 `https://api.iblai.app/dm/api/ai-mentor/orgs/$IBLAI_ORG/spend-caps/tenant/`).30 Omitting `/dm` will not resolve.31- **Header:** `Authorization: Api-Token $IBLAI_API_KEY` on every request.32- **Path vars:** `{org}` = `$IBLAI_ORG` (the org key). `{mentor}` = an agent's33 `unique_id` (a UUID). `{user_id}` / `{username}` = the target user.34- **Endpoint prefix twin:** every path under `api/ai-mentor/` also resolves35 under `api/ai-agent/` (alias); either works, pick one and be consistent.36- **Permission tiers:** all cap **configuration** endpoints (organization / agent /37 user-agent read+write) require a **platform admin** (RBAC action prefix38 `Ibl.Mentor/SpendCaps/*`). The **status** endpoint is safe to show end users: any39 authenticated member may read **their own** status, and admins may read any40 user's — it returns only a coarse zone + percent, never raw dollars.41- **PUT is upsert:** writing a cap that doesn't exist yet returns **20142 Created**; writing over an existing one returns **200 OK**.43- DELETE and PUT (destructive / budget-changing) calls say "Confirm with the44 user first."45- Not connected yet? Run **`/iblai-api-login`** first to populate `IBLAI_ORG`,46 `IBLAI_USERNAME`, and `IBLAI_API_KEY`.4748## Concepts4950- **scope** — `tenant` | `agent` | `user_agent`. Set by the endpoint you call,51 never sent in the body. Narrower scopes carry `mentor_unique_id` (agent,52 user_agent) and `username` (user_agent).53- **interval_type** — `day` | `week` | `month` | `year`. The rolling window is54 calendar aligned (day = midnight, week = Monday, month = 1st, year = Jan 1).55- **enforcement** — `block` (default): once `current_spend_usd >= max_cost_usd`56 the next chat is refused with 429. `alert_only`: never blocks; only drives57 near-limit alerts.58- **alert_thresholds** — a list of fractions in `(0, 1]` (default `[0.8, 0.95]`59 = 80% and 95%). Each fires an admin alert once per period.60- **Read-only counters** — `current_spend_usd`, `remaining_usd`, `is_exceeded`,61 `period_started_at`, `last_reconciled_at` are reconciled from ClickHouse and62 ignored if sent in a write body.63- **Applicability** — for a given chat, the organization cap always applies; the agent64 cap applies when an agent is in play; the user's own per-agent cap applies when65 both agent and user are known. When several hard-block caps are exceeded, the66 **most specific** one (user > agent > organization) is reported.6768## Reads6970### Organization cap7172- **GET** `/api/ai-mentor/orgs/{org}/spend-caps/tenant/` — the org-wide cap, or73 `404` if none is set. Returns the full cap object (see Schema).7475### Agent caps7677- **GET** `/api/ai-mentor/orgs/{org}/spend-caps/agents/[?mentor={mentor}]` — list78 agent-scoped caps for the org; pass `mentor` to filter to one agent. Returns a79 list of cap objects.80- **GET** `/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-cap/` — the single81 cap for one agent, or `404` if none. The agent is resolved org-scoped, so82 an agent from another org returns `404`.8384### User-per-agent caps8586- **GET** `/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-caps/users/[?username=&email=]`87 — list explicit per-user caps on one agent; optional `username` / `email`88 filters.89- **GET** `/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-caps/users/{username}/`90 — the one cap for a specific user on that agent, or `404`.9192### User spend status (safe to show end users)9394- **GET** `/api/ai-mentor/orgs/{org}/spend-caps/status/{user_id}/[?mentor={mentor}]`95 — the coarse status across every cap that applies to `{user_id}` (organization cap96 always; agent + that user's own per-agent cap when `mentor` is given). Any97 member may read their own; admins may read anyone's. Returns **only** a zone98 and percentage — never raw spend/limit dollars — so it is safe to surface to99 end users. Returns `400` if `mentor` is not a valid UUID.100101 ```json102 {103 "status": "warning",104 "blocked": false,105 "caps": [106 {107 "scope": "tenant",108 "interval_type": "month",109 "enforcement": "block",110 "status": "warning",111 "percent_used": 82.5,112 "warning_threshold": 0.8,113 "mentor_unique_id": null,114 "username": null115 }116 ]117 }118 ```119120 `status` is the worst zone across the applicable caps (`ok` → `warning` →121 `exceeded`); `blocked` is `true` when a hard-block cap is already exceeded (the122 next chat would be refused).123124## Writes (platform admin — confirm with the user first)125126All writes accept the same body fields: `max_cost_usd` (**required** on create,127> 0), `interval_type` (**required**), `enforcement` (default `block`),128`alert_thresholds` (default `[0.8, 0.95]`), `enabled` (default `true`). `scope`,129the agent, and the user are inferred from the URL — never the body. Counter130fields sent in the body are ignored.131132### Organization cap133134- **PUT** `/api/ai-mentor/orgs/{org}/spend-caps/tenant/` — create (201) or update135 (200) the org-wide cap.136- **DELETE** `/api/ai-mentor/orgs/{org}/spend-caps/tenant/` — remove it (`204`).137138### Agent cap139140- **PUT** `/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-cap/` — create/update141 the cap for one agent.142- **DELETE** `/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-cap/` — remove it.143144### User-per-agent cap145146- **PUT** `/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-caps/users/{username}/`147 — create/update the cap for one user on one agent (username comes from the URL,148 not the body).149- **DELETE** `/api/ai-mentor/orgs/{org}/mentors/{mentor}/spend-caps/users/{username}/`150 — remove it.151152## Example153154```bash155dm=https://api.iblai.app/dm156auth="Authorization: Api-Token $IBLAI_API_KEY"157158# Set a $200/month hard-block tenant cap that alerts at 75% and 90%.159curl -X PUT "$dm/api/ai-mentor/orgs/$IBLAI_ORG/spend-caps/tenant/" \160 -H "$auth" -H 'Content-Type: application/json' \161 -d '{"max_cost_usd": "200.00", "interval_type": "month",162 "enforcement": "block", "alert_thresholds": [0.75, 0.9]}'163164# Cap one agent at $50/week, alert-only (never blocks chats).165curl -X PUT "$dm/api/ai-mentor/orgs/$IBLAI_ORG/mentors/$MENTOR/spend-cap/" \166 -H "$auth" -H 'Content-Type: application/json' \167 -d '{"max_cost_usd": "50.00", "interval_type": "week", "enforcement": "alert_only"}'168169# How close is a user to their limits? (learner-safe, dollars hidden)170curl "$dm/api/ai-mentor/orgs/$IBLAI_ORG/spend-caps/status/$IBLAI_USERNAME/?mentor=$MENTOR" \171 -H "$auth"172173# Remove the tenant cap.174curl -X DELETE "$dm/api/ai-mentor/orgs/$IBLAI_ORG/spend-caps/tenant/" -H "$auth"175```176177## Notes178179- **Blocking is 429, not 402.** A spend cap that blocks a chat returns HTTP180 **429** with `{"error_code": "spend_cap_exceeded", ...}`; this is distinct from181 the **402** returned when a user runs out of purchased *credits* (see182 `iblai-api-billing`). A cap ceilings admin-set LLM cost; credits are a per-user183 prepaid balance.184- **Counters are eventually consistent.** `current_spend_usd` is refreshed by a185 scheduled reconcile task from ClickHouse, so it can lag real-time spend186 slightly. Enforcement reads the cached value to stay off the hot path.187- **Status hides dollars by design.** The status endpoint only exposes188 `percent_used` and a zone so a non-admin cannot read org- or agent-wide spend189 totals; use the admin read endpoints for the dollar figures.190- **One cap per (scope, agent, user).** Unique constraints allow a single organization191 cap, one cap per agent, and one cap per (agent, user); PUT upserts that single192 row.193- **Disable vs delete.** Set `enabled: false` to pause a cap without losing its194 configuration; DELETE removes it entirely.195196## Schema197198Cap object (read; write accepts the non-read-only fields):199200| Field | Type | Notes |201|---|---|---|202| `id` | int | read-only |203| `scope` | str | `tenant` \| `agent` \| `user_agent`; read-only (from URL) |204| `platform_key` | str | read-only |205| `mentor_unique_id` | str \| null | read-only; set for agent/user_agent |206| `mentor_name` | str \| null | read-only |207| `username` | str \| null | read-only; set for user_agent |208| `email` | str \| null | read-only; set for user_agent |209| `interval_type` | str | **write**, required: `day`\|`week`\|`month`\|`year` |210| `max_cost_usd` | decimal string | **write**, required on create, > 0 |211| `enforcement` | str | **write**: `block` (default) \| `alert_only` |212| `alert_thresholds` | list[float] | **write**: fractions in `(0, 1]`; default `[0.8, 0.95]` |213| `enabled` | bool | **write**: default `true` |214| `period_started_at` | datetime \| null | read-only (reconciled) |215| `current_spend_usd` | decimal string | read-only (reconciled) |216| `remaining_usd` | decimal string | read-only (`max_cost_usd - current_spend_usd`, floored at 0) |217| `is_exceeded` | bool | read-only (`current_spend_usd >= max_cost_usd`) |218| `last_reconciled_at` | datetime \| null | read-only |219| `created_at` / `updated_at` | datetime | read-only |220221Status summary (from the status endpoint):222223| Field | Type | Notes |224|---|---|---|225| `status` | str | worst zone: `ok` \| `warning` \| `exceeded` |226| `blocked` | bool | a hard-block cap is already exceeded |227| `caps[]` | list | one entry per applicable cap |228| `caps[].scope` | str | `tenant` \| `agent` \| `user_agent` |229| `caps[].interval_type` | str | `day` \| `week` \| `month` \| `year` |230| `caps[].enforcement` | str | `block` \| `alert_only` |231| `caps[].status` | str | `ok` \| `warning` \| `exceeded` |232| `caps[].percent_used` | float | 0–100+, one decimal |233| `caps[].warning_threshold` | float \| null | lowest configured alert threshold |234| `caps[].mentor_unique_id` | str \| null | set for agent/user_agent caps |235| `caps[].username` | str \| null | set for user_agent caps |