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

# Credit holds

> Stop a customer exactly when their prepaid credit runs out

> [!NOTE]
>
> Early access, with metered billing.

Reading the credit balance before each request has two gaps. It only knows the usage you have already sent, and two requests that start at the same moment both see the same credit. For expensive work, such as an AI generation or a batch of SMS, that can let a customer spend past zero.

A **hold** closes both gaps. Before the work, you reserve the credit it will need. If the credit can't cover it, the hold is refused and you stop.

## How it works

**1. Hold before the work**

```bash theme={null}
curl https://bouclay.com/api/v1/customers/ctm_…/credit-holds \
  -H "Authorization: Bearer $BOUCLAY_KEY" \
  -H "Idempotency-Key: hold-req-7f3a" \
  -H "Content-Type: application/json" \
  -d '{
    "usage": [{ "eventName": "tokens.used", "value": 20000 }],
    "expiresIn": 600,
    "reference": "req-7f3a"
  }'
```

Bouclay prices the usage the way the invoice will (tiers, discount, VAT, which credit pays first) on top of the usage so far and every other open hold. It answers:

* `201` with the hold (`hold_…`). `amount` is what it takes from the credit. Go ahead.
* `402 insufficient_credit`, with `available` and `needed`. Nothing is reserved. Stop here.
* `409 spending_cap_reached` when the usage would go past a [spending cap](https://docs.bouclay.com/billing/minimums-and-caps.md#spending-caps) that stops billing.

**2. Do the work**

**3. Commit what was actually used**

```bash theme={null}
curl https://bouclay.com/api/v1/credit-holds/hold_…/commit \
  -H "Authorization: Bearer $BOUCLAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"usage": [{ "eventName": "tokens.used", "value": 18450 }]}'
```

This records the usage as meter events and closes the hold in one step, so it is billed once. Less than held frees the rest. More is accepted and billed as ordinary usage.

If the work didn't happen, call `POST /credit-holds/{id}/release` instead.

Commit and release are safe to retry and need no `Idempotency-Key`.

## Details

* **Expiry.** `expiresIn` is in seconds: 3,600 by default, 7 days at most. A hold neither committed nor released stops counting when it expires, and you get `credit_hold.updated`. Committing it afterwards is refused with `409 hold_expired`: send the usage as a meter event instead.
* **Which meters.** Holds work on meters that `sum` their values, billed by one of the customer's active subscriptions. When those meters bill in two currencies, pass `currency`.
* **On the balance.** Open holds show as `held` on the credit balance and come off `projectedAvailable`.
* **Scopes.** Holds use the `credits` scope. Committing records usage without needing `usage:write`.


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