# `X402.Client.Budget`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/client/budget.ex#L1)

A session spend budget shared by payer clients.

A budget tracks the atomic-unit total a client has committed to paying
across requests, with a hard `:limit` and optional `:per_asset` limits.
`X402.Client.Finch.request/3` and `X402.MCP.Client.call/3` take a
`:budget` and reserve the selected amount before a payment leaves the
process, so concurrent requests cannot collectively overspend.

## Accounting

`reserve/3` atomically adds an amount to the spent total (and to the
asset's total) and fails with `{:error, {:budget_exceeded, details}}`
without changing anything when either limit would be exceeded. The
drivers reserve after the payload is built and before the paid retry is
sent, and `release/3` the reservation when the payment is not accepted:

* a transport error on the paid retry,
* an HTTP retry that answers with a non-2xx status and no successful
  `PAYMENT-RESPONSE` receipt,
* an MCP retry that answers with another payment-required result and no
  successful `_meta["x402/payment-response"]` receipt.

Everything else — a 2xx response, a tool result, or any response carrying
a `success: true` settlement receipt — counts as spent, whether or not the
facilitator actually settled: the budget is a safety cap on what the
client has authorized, not a ledger of on-chain transfers.

Auth-capture is deliberately more conservative: once a paid request is
dispatched, its entire maximum remains reserved for every outcome, including
errors and renewed challenges. A hold or charge may already exist. Only
application reconciliation against trusted payment state should release it.

Amounts are atomic units: non-negative integers or integer strings, as in
`PaymentRequirements.amount`. Assets are compared case-insensitively.

## Example

    {:ok, budget} = X402.Client.Budget.start_link(limit: "5000000", per_asset: %{usdc => "1000000"})

    X402.Client.Finch.request(MyApp.Finch, url, signer: signer, budget: budget)

    X402.Client.Budget.spent(budget)
    #=> %{total: 10000, per_asset: %{"0x036cbd53842c5426634e7929541ec2318f3dcf7e" => 10000}}

# `amount`

```elixir
@type amount() :: non_neg_integer() | String.t()
```

An atomic-unit amount: a non-negative integer or integer string.

# `budget`

```elixir
@type budget() :: GenServer.server()
```

A budget process reference.

# `exceeded`

```elixir
@type exceeded() :: %{
  scope: :total | :asset,
  asset: String.t(),
  amount: non_neg_integer(),
  limit: non_neg_integer(),
  spent: non_neg_integer()
}
```

Why a reservation was refused.

# `reserve_error`

```elixir
@type reserve_error() :: {:budget_exceeded, exceeded()} | :invalid_amount
```

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `release`
*since 0.9.0* 

```elixir
@spec release(budget(), String.t(), amount()) :: :ok | {:error, :invalid_amount}
```

Releases a previous reservation of `amount` of `asset`.

Releases at most the amount held for this asset, reducing both its bucket
and the total by the same amount. Other assets' reservations are unaffected;
totals never go below zero.

## Examples

    iex> {:ok, budget} = X402.Client.Budget.start_link(limit: 100)
    iex> :ok = X402.Client.Budget.reserve(budget, "usdc", 60)
    iex> X402.Client.Budget.release(budget, "usdc", 60)
    :ok
    iex> X402.Client.Budget.spent(budget)
    %{total: 0, per_asset: %{"usdc" => 0}}

# `reserve`
*since 0.9.0* 

```elixir
@spec reserve(budget(), String.t(), amount()) :: :ok | {:error, reserve_error()}
```

Reserves `amount` of `asset` against the budget.

Returns `{:error, {:budget_exceeded, details}}` — with `details.scope`
telling whether the total or the asset limit was hit — and leaves the
budget unchanged when the reservation does not fit.

## Examples

    iex> {:ok, budget} = X402.Client.Budget.start_link(limit: 100)
    iex> X402.Client.Budget.reserve(budget, "usdc", "60")
    :ok
    iex> X402.Client.Budget.reserve(budget, "usdc", 50)
    {:error, {:budget_exceeded, %{scope: :total, asset: "usdc", amount: 50, limit: 100, spent: 60}}}
    iex> X402.Client.Budget.reserve(budget, "usdc", "0.5")
    {:error, :invalid_amount}

# `retain_after_dispatch?`
*since 0.9.0* 

```elixir
@spec retain_after_dispatch?(map()) :: boolean()
```

Whether a scheme's exposure survives every dispatched-request outcome.

## Examples

    iex> X402.Client.Budget.retain_after_dispatch?(%{"accepted" => %{"scheme" => "auth-capture"}})
    true

    iex> X402.Client.Budget.retain_after_dispatch?(%{"accepted" => %{"scheme" => "exact"}})
    false

# `spent`
*since 0.9.0* 

```elixir
@spec spent(budget()) :: %{
  total: non_neg_integer(),
  per_asset: %{required(String.t()) =&gt; non_neg_integer()}
}
```

Returns the amounts currently reserved or spent.

Asset keys are lowercased.

## Examples

    iex> {:ok, budget} = X402.Client.Budget.start_link(limit: 100, per_asset: %{"USDC" => 80})
    iex> :ok = X402.Client.Budget.reserve(budget, "USDC", 30)
    iex> X402.Client.Budget.spent(budget)
    %{total: 30, per_asset: %{"usdc" => 30}}

# `start_link`
*since 0.9.0* 

```elixir
@spec start_link(keyword()) :: GenServer.on_start()
```

Starts a budget process.

## Options

* `:name` (`t:term/0`) - Optional `GenServer` name to register the budget under.

* `:limit` - Required. Total spend limit in atomic units (integer or integer string).

* `:per_asset` - Per-asset spend limits in atomic units, keyed by asset identifier. The default value is `%{}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
