# Budgets

Spendline runs two budget systems at once, deliberately. Both are evaluated on
every proxied call.

| System | Collection | Scope |
|---|---|---|
| Flat account budget | `budgets` | One monthly cap for the whole account. |
| Hierarchical budgets | `hierarchical_budgets` | org → team → agent → customer. See [hierarchical-budgets](https://www.spendline.ai/agents/hierarchical-budgets.md). |

## Enforcement happens before the spend

A budget check runs **before** the upstream provider call. A blocked call:

- returns **HTTP 402**,
- never reaches the provider,
- costs nothing.

That is the whole point. Your integration must **not** retry a 402 against the
provider directly.

```python
try:
    resp = client.chat.completions.create(...)
except openai.APIStatusError as e:
    if e.status_code == 402:
        raise BudgetExceeded("AI budget reached for this scope") from e   # surface it
    if e.status_code == 403:
        raise PolicyBlocked("Model or token policy blocked this call") from e
    raise
```

## Pre-call cost estimation

Because the real output size is unknown before the call, enforcement projects
cost using the request's output cap (`max_tokens` / `max_output_tokens`), or a
default floor when none is supplied. Setting a realistic `max_tokens` makes
enforcement more accurate.

## Reading budgets

```bash
curl https://www.spendline.ai/api/budgets \
  -H "x-spendline-key: $SPENDLINE_API_KEY"
```

```bash
# every scope with current spend against limit
curl https://www.spendline.ai/api/budgets/summary/all \
  -H "x-spendline-key: $SPENDLINE_API_KEY"
```

```bash
# which agents / customers / teams have traffic this month, 
# use this to discover the scope_ids worth budgeting
curl https://www.spendline.ai/api/budgets/scopes \
  -H "x-spendline-key: $SPENDLINE_API_KEY"
```

## Creating a budget requires owner or admin authority

`POST /api/budgets` is gated by owner-or-admin. A **scoped child key**: the kind
issued to an agent by the onboarding flow, cannot create budgets, by design.

Two correct paths:

1. **Propose it during onboarding.** Include the budget you want in the
   onboarding request; the human sees it on the approval page and approves it,
   and Spendline creates it under their authority. See
   [onboarding](https://www.spendline.ai/agents/onboarding.md).
2. **Ask the human to create it** in the dashboard, or run the call yourself with
   an owner credential the human has explicitly given you for that purpose.

Do not attempt to escalate. `POST /api/budgets` with a child key returns
HTTP 403 `permission_denied`.

## Request shape, for reference

```http
POST /api/budgets
x-spendline-key: <owner or admin credential>
Content-Type: application/json

{
  "scope_type": "agent",              // org | team | agent | customer
  "scope_id": "support-bot",          // required unless scope_type is "org"
  "monthly_limit_usd": 500,
  "strict_mode": true,
  "require_approval_on_exceed": false
}
```

- `409` means a budget already exists for that scope. Use `PUT /api/budgets/:id`.
- `strict_mode` controls whether the cap blocks (`true`) or only records.

## Raising, deleting, overriding

Raising a limit, deleting a budget, and approving a budget override are all
**human-authorized** actions. They are deliberately absent from Spendline's MCP
tool surface: they remove the control the product exists to hold. Override
requests exist (`/api/budgets/overrides`) precisely so that raising a cap leaves
an audit trail with an approver.

## Audit trail

Every create/update/delete appends to `budget_audit_log` in the same operation as
the change, with actor and tenant. It is append-only, a correction is a new row,
never an edit.
