---
title: "Set budgets and track costs"
description: "Cap how often and how much each billing entity spends on metered AI, and react when its credit balance runs low or out."
canonical_url: "https://nuxt-backend.dev/docs/ai/costs"
---
# Set budgets and track costs

> Cap how often and how much each billing entity spends on metered AI, and react when its credit balance runs low or out.

Credits already make metered AI prepaid: a call that does not fit the balance is refused. Two limits sit on top, both through the rate limiter the scaffold sets up, and two hooks tell you when a balance runs low.

## Limit calls

The `ai` limit caps calls per billing entity. `DEFAULT_LIMITS.ai` (30 per minute per billing entity, burst 10) is the package default; override it or add your own in [`setupRateLimiter`](https://nuxt-backend.dev/docs/ship/production/rate-limits) and name it in `limit`. `DEFAULT_LIMITS.aiBudget` is the window a `budget` records under — the `units` / `period` you pass to `setupAi` are what it enforces:

```ts [backend/rateLimiter.ts]
export const rateLimiter = setupRateLimiter(components, {
  ai: { kind: 'token bucket', rate: 30, period: 60_000 },
  imageGen: { kind: 'fixed window', rate: 5, period: 60_000 },
})
```

## Cap spend per window

`setupAi({ budget })` caps credits, not calls: `{ units, period, limit? }` allows `units` credits per `period` milliseconds for each billing entity, so `{ units: 500, period: DAY }` is 500 credits a day per workspace. It needs `rateLimiter` and records the window under `limit` (default `'aiBudget'`; name your own to keep a separate budget per feature).

A budget is a cap, not a ledger: like every rate limit, consumption is never given back, so a run that fails, or settles under its estimate, still spent the estimate against the window. A call whose cost exceeds `units` is refused outright, since it could never fit. Over the cap, a call throws `Credit budget reached — N credits per window. Resets in Ns.`

## React to a low balance

`setupAi({ credits })` runs two hooks after a spend settles: `onCreditsLow` when the balance crosses `lowBalanceThreshold` downwards, and `onCreditsExhausted` when it goes from positive to zero or below. Both fire on the crossing only, so a second spend that stays below the line is silent, and they are safe to wire straight to a notification. The event carries `entityId`, `userId`, `meter` / `meterId`, `balance`, `previousBalance`, `spent` and `threshold`. A hook that throws is logged and the spend stays settled.

```ts [backend/ai.ts]
// The scaffolded `ai`, plus a budget and the credits hooks.
import { DAY } from 'nuxt-backend/rate-limit'

export const ai = setupAi(components, {
  billing,
  rateLimiter,
  budget: { units: 500, period: DAY },   // 500 credits a day per workspace, on top of the `ai` call-rate limit
  credits: {
    lowBalanceThreshold: 50,
    onCreditsLow: async (ctx, { entityId, balance }) => {
      // record it, or hand it off — the lifecycle emails own the sending
    },
  },
})
```

In the browser, `<CreditsLowBanner>` shows the same moment to the person: see [Credits](https://nuxt-backend.dev/docs/billing/credits).

<page-keys>

- Try it live in the playground: [/playground/platform/ai](https://nuxt-backend.dev/playground/platform/ai)
- API reference: [/docs/reference/api/ai](https://nuxt-backend.dev/docs/reference/api/ai)
- Source: [https://github.com/qruto/nuxt-backend/blob/main/src/convex/integrations/ai.ts](https://github.com/qruto/nuxt-backend/blob/main/src/convex/integrations/ai.ts)

</page-keys>


## Sitemap

See the full [sitemap](https://nuxt-backend.dev/sitemap.md) for all pages.
