> ## Documentation Index
> Fetch the complete documentation index at: https://docs.frogbot.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# AI policy

> Limit each user's AI models and monthly spend in FrogBot.

The AI policy plugin gives each user a list of allowed models, an optional monthly budget in USD, and a spend counter. FrogBot checks them on every AI call the user makes, adds the cost of each call to their spend, and resets spend once a month.

## Installation

```bash theme={"theme":"tokyo-night"}
pnpm add @frogbotai/plugin-ai-policy
```

## Configuration

```ts theme={"theme":"tokyo-night"}
import { aiPolicyPlugin } from '@frogbotai/plugin-ai-policy'
import { buildConfig } from 'frogbot'

export default buildConfig({
  collections: [{ slug: 'users', auth: true, fields: [] }],
  ai: {
    defaultModel: 'openai/gpt-5.4-mini',
    providers: { openai: true },
  },
  plugins: [aiPolicyPlugin()],
})
```

The plugin needs an `ai` configuration and fails at startup without one. It adds its fields to your auth collection. With several auth collections, it uses `admin.user`; otherwise set `authCollection`. Users of other auth collections get no model list, budget or spend tracking. When your app has no auth collection, the plugin adds a `users` collection with a `name` field and the policy fields. It runs with `order: 10`.

## Fields

| Field | Label | Description |
| - | - | - |
| `models` | Models | Model IDs and [alias](https://docs.frogbot.ai/ai/overview.md#add-aliases) slugs the user may call, chosen from every configured model and alias. Leave it empty to allow all models, including ones you configure later. |
| `monthlyBudget` | Monthly Budget (USD) | Spend limit for the month, at least `0`. Leave it empty for no limit. |
| `spendThisPeriodUSD` | Monthly Spend (USD) | What the user has spent this month. Read-only and starts at `0`; the plugin maintains it. |

`monthlyBudget` and `spendThisPeriodUSD` are USD [Money fields](https://docs.frogbot.ai/fields/money.md).

An entry in `models` is an exact match: allowing an alias allows calls to the alias, not direct calls to the model behind it.

Define a field with the same name on your auth collection to change it. Your field replaces the plugin's settings it sets, and its `admin` and `access` are merged over the plugin's. For example, let only admins change budgets:

```ts theme={"theme":"tokyo-night"}
{
  slug: 'users',
  auth: true,
  fields: [
    {
      name: 'monthlyBudget',
      type: 'number',
      access: { update: ({ req }) => req.user?.role === 'admin' },
    },
  ],
}
```

Apart from the spend counter, the fields have no access rules of their own, so users can edit their own models and budget until you add one. Use [Roles](https://docs.frogbot.ai/plugins/roles.md) to grant the edit to one role.

## Enforcement

The plugin wraps each [`ai.access`](https://docs.frogbot.ai/ai/overview#control-access) rule (`generate`, `embed`, `transcribe`, `rerank`, `evaluate`). For each call, your own rule runs first; without one, the user must be signed in. When it allows a signed-in user, a non-empty `models` list must include the model or alias the caller asked for. Otherwise the call fails with a `403` and the code `model_not_allowed`: `Model "<id>" is not allowed by this policy.` Over HTTP, this returns `{ "error": { "message": "...", "type": "invalid_request_error", "code": "model_not_allowed", "param": "model" } }`, the same body as a spent budget; on `/api/v1/messages` it is Anthropic's `{ "type": "error", "error": { "type": "permission_error", "message": "...", "param": "model" } }`.

The same check shapes chat. A user sees only the agent models they may use, and an agent with none is hidden from them. `GET /api/v1/models` likewise lists only the models in a non-empty `models` list. Chat shows a microphone only when the user may use the [transcription model](https://docs.frogbot.ai/chat/ui-chat.md#voice-input), and a read aloud button only when they may use the [speech model](https://docs.frogbot.ai/chat/ui-chat.md#read-aloud); for an alias, list the model behind it.

The budget is checked when a call runs, before every AI call a signed-in user makes, agent turns included. When `monthlyBudget` is set and `spendThisPeriodUSD` has reached it, the call fails with a `403` and the code `budget_exceeded`: `The configured budget has been exhausted.` A spent budget doesn't hide agents or models: chat lists them as usual and shows this error when the user sends. Calls without a signed-in user, such as scheduled tasks, have no per-user limit.

## Spend

After each AI call by a signed-in user, the plugin adds its cost to that user's `spendThisPeriodUSD`, priced from the model catalog or your [custom model prices](https://docs.frogbot.ai/ai/overview.md#custom-model-options). Failed calls and calls that cost nothing add nothing. If the spend update fails, FrogBot logs the error and the call still succeeds.

Spend updates for one user run one at a time within a process. Calls already running when the budget is reached still finish, and several server processes can update the same user at once, so treat the budget as a limit that can be exceeded by a few calls, not an exact cap.

## Monthly reset

The `plugin-ai-policy-reset-spend` task sets every user's `spendThisPeriodUSD` to `0` at midnight on the first day of each month (`0 0 1 * *`), on its own `plugin-ai-policy-reset-spend` queue. Run a [worker](https://docs.frogbot.ai/jobs-queue/workers.md) that handles schedules for that queue, such as one started with `--all-queues --handle-schedules`.

## API key limits

With the [API keys plugin](https://docs.frogbot.ai/plugins/api-keys.md) on the same auth collection, each key gets its own limits, inside its owner's. List the plugins in any order.

| Field | Label | Description |
| - | - | - |
| `models` | Models | Models the key may call, chosen from the owner's. Leave it empty to use all of the owner's models. |
| `monthlyBudget` | Monthly Cap (USD) | Spend limit for the key this month, up to the owner's monthly budget. Leave it empty for no cap. |
| `spendThisPeriodUSD` | Monthly Spend (USD) | What the key has spent this month. Read-only; the plugin maintains it. |

The key's create dialog has a collapsed **Limits** section with the model list and cap, and the keys list shows spend and cap after the prefix. A key's models and cap are checked against its owner when they change, for admins too: `"<id>" isn't allowed for <owner>.` and `Can't exceed <owner>'s monthly budget of $50.00.` Values that didn't change aren't checked again, so lowering the owner's budget doesn't block renaming a key.

Only a signed-in session can change a key's limits. A request authenticated by an API key that changes `models` or `monthlyBudget` on any key gets a `403`: `API keys can't change their own limits. Sign in to change them.`

On a request made with a key, the owner's model list and budget are checked first, then the key's:

* a model outside a non-empty key list fails with a `403`, code `model_not_allowed`: `Model "<id>" is not allowed for API key "<name>".` Chat and agent lists, and `GET /api/v1/models`, show only the key's models;
* a spent cap fails with a `403`, code `budget_exceeded`: `API key "<name>" has used its monthly cap of $25.00.`

Both fail before the model is called, so they write no usage row. Each call's cost is added to the key's and the owner's spend, agent runs included, and the monthly reset zeros keys too. Rotating a key keeps its models, cap and spend.

## Use your own policy

The plugin is built only on `ai.access` and AI hooks, so you can replace it with your own rules, such as prepaid credits. Leave the plugin out and put your check in `ai.access`. Each rule receives `{ req, model }`; return `false` for a generic `403`, or throw an error with a numeric `status` and a `code` to send your own:

```ts theme={"theme":"tokyo-night"}
class OutOfCreditsError extends Error {
  status = 402
  code = 'insufficient_credits'
}

const hasCredits = ({ req }) => {
  if (!req.user) return false
  if ((req.user.credits ?? 0) <= 0) throw new OutOfCreditsError('You have no credits left.')

  return true
}

export default buildConfig({
  // ...
  ai: {
    providers: { openai: true },
    access: { generate: hasCredits, embed: hasCredits, transcribe: hasCredits },
  },
})
```

To charge for calls, add an `afterOperation` [hook](https://docs.frogbot.ai/ai/overview.md#observe-calls) that prices the call's usage with `calculateUsageCostUSD({ model, providers, usage })` from `frogbot` and updates the user.

## Options

| Option | Type | Default | Description |
| - | - | - | - |
| `authCollection` | `string` | Your only auth collection, or `admin.user` when there are several | Slug of the auth collection that gets the policy fields. Required when you have several auth collections and `admin.user` is not one of them. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.