---
title: "Spend credits in your functions"
description: "Spend credits from your own server code, in one call or as reserve, run and settle, and give them back on a sum meter."
canonical_url: "https://nuxt-backend.dev/docs/billing/credits/spending"
---
# Spend credits in your functions

> Spend credits from your own server code, in one call or as reserve, run and settle, and give them back on a sum meter.

Spend from your **own** server code when a metered feature is used — never from the client:

```ts [backend/generate.ts]
import { v } from 'convex/values'
import { action } from './_generated/server'
import { billing } from './billing'

export const generate = action({
  args: { prompt: v.string() },
  handler: async (ctx, { prompt }) => {
    // The billing entity (active workspace, or user with billTo: 'user') resolves from identity.
    await billing.spendCredits(ctx, { meter: 'credits', value: 1, name: 'credits' })
    return runModel(prompt)
  },
})
```

`spendCredits(ctx, event)` takes a `SpendCreditsEvent`: `meter` (catalog key) or a raw `meterId`, `value` (default `1`), `name` (the provider event name — defaults to the meter's `eventName` / key), optional `userId` to spend for a specific entity, `allowOverage` (let the balance go negative — the pay-as-you-go case below; off by default), `metadata`, `timestamp`, `externalId` (the idempotency key — a random UUID by default).

Every amount must be a finite number above zero — `value` here and in `refundCredits`, and a reservation's `value` when it settles — and a settle's `finalAmount` zero or more. Anything else throws before the cached balance or the provider sees it: a negative spend would credit the balance and ingest a usage event that grants credits, and a `NaN` would leave a balance that every later guard waves through. The cache's own mutations refuse the same amounts, and a balance that is not a number refuses spends.

<note>

The scaffolded [`ai.ts`](https://nuxt-backend.dev/docs/ai/metered-actions) wraps exactly this into `ai.meteredAction` and `ai.stream`, adding rate limiting and `ctx.usage` — reach for those before hand-rolling spends.

</note>

## Reserve → run → settle

A spend is three steps, and `spendCredits` is the one-call form of them:

1. **reserveCredits** — an atomic conditional decrement of the **cached** balance inside the component. Convex mutations serialize, so two concurrent spends can never both pass, and there is no live provider read on the spend path. An insufficient balance **throws** `insufficient_credits` with `balance` and `need` ([Errors](https://nuxt-backend.dev/docs/reference/backend/errors#insufficient_credits)), so credits are strictly prepaid — never billed as overage, unless the spend opts in with `allowOverage: true` (the pay-as-you-go case: a meter with no credit benefit behind it has nothing prepaid to draw down, so every unit is overage the provider invoices at the end of the cycle). A cold cache (granted but never synced) self-heals once from an action ctx (`allowRefresh: false` skips that from a mutation).
2. **settleSpend** — ingests the provider usage event (the only durable record, visible per customer in the provider's portal) and finalizes the reservation. Pass `finalAmount` when the actual cost came in under the estimate and the remainder goes back to the balance in the same transaction; it is clamped to the reservation, because settling above it would be a second, unguarded debit. A `finalAmount` of `0` ingests nothing and hands the whole reservation back. A failed ingest releases the whole reservation.
3. **releaseSpend** — undoes a reservation whose work failed. Nothing is charged.

Split like this so work can run *between* the guard and the charge — `setupAi` reserves before running a model and settles only after success; the streaming endpoint even settles in a different function than the one that reserved:

```ts
const reservation = await billing.reserveCredits(ctx, { meter: 'credits', value: 5 })
try {
  const result = await runModel(prompt)
  await billing.settleSpend(ctx, reservation)
  return result
}
catch (error) {
  await billing.releaseSpend(ctx, reservation)
  throw error
}
```

A `SpendReservation` is plain serializable data (`entityId`, `externalId`, `reserved`, `meter`, `meterId`, `value`), so it can cross function boundaries — and because it can, `settleSpend` checks its `value` again rather than ingesting on its word.

## Sum meters vs count meters

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      <code>
        aggregation: 'sum'
      </code>
      
       (default)
    </th>
    
    <th>
      <code>
        aggregation: 'count'
      </code>
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      One event means
    </td>
    
    <td>
      <code>
        metadata[property]
      </code>
      
       credits (default property <code>
        amount
      </code>
      
      )
    </td>
    
    <td>
      exactly 1 credit
    </td>
  </tr>
  
  <tr>
    <td>
      Multi-credit spends
    </td>
    
    <td>
      <code>
        value: 5
      </code>
      
       in one event
    </td>
    
    <td>
      throws — spend <code>
        1
      </code>
      
       per event
    </td>
  </tr>
  
  <tr>
    <td>
      Refunds
    </td>
    
    <td>
      <code>
        refundCredits
      </code>
      
       ingests a compensating negative event
    </td>
    
    <td>
      not possible
    </td>
  </tr>
</tbody>
</table>

```ts
// Give credits back on a sum meter (e.g. a generation the user rejected)
await billing.refundCredits(ctx, { meter: 'credits', value: 5 })
```


## Sitemap

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