# Budgets and alerts

## Two kinds of budget

| | SDK budget | Cloud budget |
|---|---|---|
| Set with | `veqen.budget(...)` in code | The Budgets page, the MCP `set_budget` tool |
| Counts spend | In this process | Across every process sending to your organization |
| Enforced | Before a wrapped call, using recorded spend | After ingestion, budget evaluation and SDK polling (every 30 seconds by default) |
| Alerts | – | Email, Slack and webhook at 50%, 80% and 100% |

Use both: SDK budgets check spend in one process; cloud budgets combine the usage received across your organization. Neither guarantees a spending cap. A call can cross the limit before its cost is recorded, and concurrent or in-flight calls can add more spend. Local counters reset when the process restarts. Delayed or failed ingestion and polling delay cloud enforcement; SDKs retain their last known cloud state when polling fails.

## Scopes

| Scope | Value | Meaning |
|---|---|---|
| Organization | – | All spend |
| Customer | `*` | Every customer gets the limit separately |
| Customer | `acme` | Only Acme |
| Feature | `*` or a name | Per feature |
| User | `*` or an id | Per end user, the usual guard for free plans |
| Project | `*` or a name | Per team, program, pipeline or cost center |

Periods are calendar months or days, in UTC. Calls without the tag in question don't count toward a `*` budget for that tag.

## Modes

- **Alert**: notifies at 50%, 80% and 100%. Calls continue.
- **Block**: also lists the value as breached at 100%. Every SDK using your key then raises `BudgetExceeded` for calls with that tag, before the request is sent, until the period resets or you raise the limit.

```python [Python]
try:
    client.responses.create(model="gpt-5-mini", input=prompt)
except veqen.BudgetExceeded as exc:
    return f"You've used this month's AI allowance (${exc.limit_usd:.2f}). Upgrade for more."
```

```ts [TypeScript]
try {
  await client.responses.create({ model: "gpt-5-mini", input: prompt });
} catch (error) {
  if (error instanceof BudgetExceeded) return upgradePrompt(error.limitUsd);
  throw error;
}
```

Spend that arrives through OpenTelemetry or `record()` counts toward budgets, but only the SDK wrappers can stop a call before it happens.

## Where alerts go

Owners and admins get email. On Pro and Team, add a Slack incoming webhook and a signed webhook under Settings › Alerts. Webhook bodies look like:

```json
{"type": "budget.threshold", "org_id": 42, "subject": "Budget Free users at 80%",
 "text": "Spent this month, of $5.00: customer acme: $4.00.",
 "data": {"budget_id": 7, "scope": "customer", "limit_usd": 5.0, "threshold": 80, "blocking": false,
          "crossed": [{"value": "acme", "spent_usd": 4.0}]},
 "url": "https://veqen-app.com/app/budgets"}
```

Each alert is sent once; a failed delivery is logged, not retried. Verify the `Veqen-Signature: t=<unix>,v1=<hex>` header: compute HMAC-SHA256 of `<t>.<raw body>` with your webhook secret and compare in constant time.

The same channels carry spend-spike alerts (a day at twice the previous week's average and over $25), credit-expiry warnings, connection failures and the Monday digest.
