curl --request GET \
--url https://{host}/api/llm-gateway/admin/budgets \
--header 'Authorization: Bearer <token>'import requests
url = "https://{host}/api/llm-gateway/admin/budgets"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://{host}/api/llm-gateway/admin/budgets', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{host}/api/llm-gateway/admin/budgets",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://{host}/api/llm-gateway/admin/budgets"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://{host}/api/llm-gateway/admin/budgets")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://{host}/api/llm-gateway/admin/budgets")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body[
{
"action_on_exhaust": "block",
"alert_thresholds": [
123
],
"enabled": true,
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"org_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"period": "daily",
"scope_type": "org",
"token_limit": 123,
"traffic_type": "all",
"cost_limit": 123,
"created_at": "2023-11-07T05:31:56Z",
"currency": "<string>",
"member_of_group": "<string>",
"scope_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"scope_value": "<string>",
"target_mcp_server_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"target_model_alias": "<string>",
"target_provider_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"target_upstream_model": "<string>",
"created_by_email": "<string>",
"created_by_name": "<string>",
"created_by_user_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"last_change_note": "<string>",
"last_modified_at": "2023-11-07T05:31:56Z",
"last_modified_by_email": "<string>",
"last_modified_by_name": "<string>",
"last_modified_by_user_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
]List the organization's token budgets
curl --request GET \
--url https://{host}/api/llm-gateway/admin/budgets \
--header 'Authorization: Bearer <token>'import requests
url = "https://{host}/api/llm-gateway/admin/budgets"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://{host}/api/llm-gateway/admin/budgets', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{host}/api/llm-gateway/admin/budgets",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://{host}/api/llm-gateway/admin/budgets"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://{host}/api/llm-gateway/admin/budgets")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://{host}/api/llm-gateway/admin/budgets")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body[
{
"action_on_exhaust": "block",
"alert_thresholds": [
123
],
"enabled": true,
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"org_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"period": "daily",
"scope_type": "org",
"token_limit": 123,
"traffic_type": "all",
"cost_limit": 123,
"created_at": "2023-11-07T05:31:56Z",
"currency": "<string>",
"member_of_group": "<string>",
"scope_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"scope_value": "<string>",
"target_mcp_server_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"target_model_alias": "<string>",
"target_provider_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"target_upstream_model": "<string>",
"created_by_email": "<string>",
"created_by_name": "<string>",
"created_by_user_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"last_change_note": "<string>",
"last_modified_at": "2023-11-07T05:31:56Z",
"last_modified_by_email": "<string>",
"last_modified_by_name": "<string>",
"last_modified_by_user_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
]Authorizations
A Barndoor API key, sent as Authorization: Bearer <key>. Keys are prefixed bdai_ and are opaque — do not attempt to decode one. Interchangeable with an OAuth 2.0 access token.
Response
The requested record
block, throttle, warn How often a budget's counter resets.
Every period boundary is UTC, with no per-org configuration:
key_suffix formats Utc::now(), so Daily rolls at 00:00 UTC, Weekly
at Monday 00:00 UTC (ISO-8601 week), and Monthly on the 1st at 00:00 UTC.
An admin in a western timezone therefore sees their "daily" cap reset in
the afternoon — [Self::period_end] exists so the UI can say so rather
than leave them to infer it (BCP-4204).
daily, weekly, monthly Scope is the identity axis of a budget or rate-limit policy — "who is
this policy for?". The what (LLM provider, upstream model name, model
alias) lives on dedicated target_* columns on the row (BCP-2813), not
as a scope value. LlmProvider and Model remain in the enum because
rate_limit_policies and model_access_policies still accept them in
their scope_type CHECK constraints (V12 / V18 / V22 widenings — a
separate scope of work to clean up). For token budgets the CHECK was
tightened back to identity-only by V41__budget_scope_target.sql, so
any LlmProvider/Model budget row that survived would simply fail to
match in BudgetEnforcer::matched_keys.
org, team, user, project, api_key, mcp_server, llm_provider, model, agent, role, group all, llm, mcp Optional spending cap in the configured currency. When set, the budget is enforced on estimated cost in addition to tokens.
When the budget was created. Populated on the admin API path (where it
is surfaced in the dashboard); left None on the hot enforcement path
and in fixtures, which don't need it. #[serde(default)] keeps older
payloads without the field deserializing cleanly.
ISO 4217 currency code (e.g. "USD"). Defaults to "USD".
BCP-3814 membership filter: when set, this budget only applies to requesters whose IdP group membership includes this group name.
Only meaningful on a broad user-scoped budget (scope_type == User
with scope_id and scope_value both None), where the counter
qualifier already resolves to the requesting user's id — so one budget
row gives every member of the group their own independent allowance,
and a user joining the group is enforced on their next request with no
admin action. DB-enforced by
token_budgets_member_of_group_requires_broad_user (V65, tightened to
exclude scope_value by V67).
None ⇒ the budget applies org-wide (the pre-BCP-3814 behaviour).
Contrast with a ScopeType::Group budget, which keys its counter by
the group name and so pools one allowance across all members.
BCP-4053 Target dimension: when set, the budget only fires on requests
routed to this MCP server. The MCP-traffic counterpart of
target_provider_id, and mutually exclusive with all three LLM target
columns (DB-enforced by token_budgets_mcp_target_exclusive_check) —
no single request carries both an MCP server and an LLM route, so a
row naming both could never match.
Distinct from a ScopeType::McpServer budget, which puts the server on
the identity axis and so cannot be combined with a user or group.
As a target it composes freely with any scope, which is what makes
"this user's cap on this server" expressible.
BCP-2813 Target dimension: when set, the budget only fires when the
requested model alias (request.model) equals this value. Mutually
exclusive with the other two target_* fields.
BCP-2813 Target dimension: when set, the budget only fires on routes
whose provider id equals this value. May be combined with
target_upstream_model to cap a single model on this provider;
mutually exclusive with target_model_alias (DB-enforced by
token_budgets_alias_target_exclusive_check).
BCP-2813 Target dimension refining target_provider_id: when set,
the budget only fires on routes whose resolved upstream model name
equals this value (string match). Always set alongside a provider
(V54 CHECK-enforced) — a bare upstream name would string every
provider serving that model into one cap.
Optional free text explaining why the last change was made ("Raised for the Q3 batch job", "Vendor deprecated the old endpoint").
Admin-authored and rendered in the portal, so it is untrusted input: length-capped here, escaped on render.
When the change named by last_modified_by_* happened.
Its own column rather than the row's updated_at, because updated_at
is bumped by writes nobody is attributed for -- pairing the two would
date a named admin's edit to a background job's clock. See the V75
migration for the full reasoning.