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

# How credits and billing work

> Account-pool credit model, hold lifecycle, and balance queries

mintfax uses a prepaid credit system. You purchase credit packages, credits are consumed per page when faxes are delivered, and the money lives in pools at the **account** level - not per environment.

## Account-pool model

Every account has exactly two credit balance rows:

| Pool      | What it holds            | Funding                                       |
| --------- | ------------------------ | --------------------------------------------- |
| `live`    | Real credits, real money | Stripe charges to the card on file            |
| `sandbox` | Simulation credits       | Seeded from a `CreditPackage` at registration |

These correspond to the two valid values of the `balance_type` field returned on wallet objects. There is no third pool, and no per-environment pool.

## Environments share their type's pool

If your account has five sandbox environments, they all draw from the same sandbox pool. The same is true for live. A credit purchase made in any live environment funds the entire account's live activity. Likewise, a fax sent from any live environment deducts from that one shared live pool.

This is intentional: credits are a billing concern, and billing happens at the account. Environments are an organizational and isolation concern, not a wallet boundary.

<Note>
  A environment is a sandbox environment or a live environment for its entire life - it never crosses the wall. So each environment touches exactly one pool. There is no need to choose which pool a fax draws from; the environment type determines it.
</Note>

## Hold, capture, release

When you submit a fax, credits move through a three-step lifecycle against the relevant pool:

1. **Hold** - a credit hold is placed immediately on submission, reserving the estimated cost based on page count. The held amount is deducted from `available` and added to `held`.
2. **Capture** - when the fax is delivered successfully, the hold is captured as a permanent charge. The held amount is removed from `held`.
3. **Release** - if delivery permanently fails, the hold is released back to `available`. You are never charged for failed deliveries.

```
Submit fax         ->  available decreases, held increases
Delivery succeeds  ->  held decreases (captured as charge)
Delivery fails     ->  held decreases, available increases (released)
```

The hold is on the **account pool**, not the environment. Two live environments under the same account see the same `available` and `held` numbers because the money is shared.

## Reading balances

mintfax exposes two balance endpoints with deliberately different shapes.

### `GET /account/balance` (account key)

Returns **both** balances as an array. Use this with an `mfx_acct_...` key when you want a complete view of the account's money.

```bash theme={null}
curl https://api.mintfax.com/v1/account/balance \
  -H "Authorization: Bearer mfx_acct_qpr456stv789..."
```

```json theme={null}
{
  "data": [
    {
      "type": "live",
      "available": 9550,
      "held": 0,
      "auto_top_up": {
        "enabled": true,
        "threshold_pct": 20,
        "package_id": 3
      }
    },
    {
      "type": "sandbox",
      "available": 4550,
      "held": 450,
      "auto_top_up": {
        "enabled": false,
        "threshold_pct": 20,
        "package_id": null
      }
    }
  ]
}
```

### `GET /environment/balance` (environment key)

Returns the **single** balance for the caller's type. Use this with `mfx_test_...` (sandbox) or `mfx_live_...` (live) when you only need the slice your environment operates against.

```bash theme={null}
curl https://api.mintfax.com/v1/environment/balance \
  -H "Authorization: Bearer mfx_test_abc123def456..."
```

```json theme={null}
{
  "type": "sandbox",
  "available": 4550,
  "held": 450,
  "auto_top_up": {
    "enabled": false,
    "threshold_pct": 20,
    "package_id": null
  }
}
```

| Field         | Description                                                            |
| ------------- | ---------------------------------------------------------------------- |
| `type`        | `live` or `sandbox` - which pool this balance row belongs to           |
| `available`   | Credits available for new faxes, in cents. Holds are already deducted. |
| `held`        | Credits reserved for in-flight faxes, in cents.                        |
| `auto_top_up` | Per-pool auto-top-up configuration (see below)                         |

A `held` value of `0` means no faxes are in flight on this pool. A non-zero `held` means one or more faxes are queued or transmitting; the reserved credits will be captured or released once each fax reaches a terminal state.

## Reading transactions

The two transaction endpoints have a deliberate asymmetry.

### `GET /account/transactions` (account key)

Returns the **pool-wide ledger** across all environments of the relevant type(s). Optionally filter with `?type=live` or `?type=sandbox`.

```bash theme={null}
curl 'https://api.mintfax.com/v1/account/transactions?type=live' \
  -H "Authorization: Bearer mfx_acct_qpr456stv789..."
```

This includes activity from every environment of that type under the account.

### `GET /environment/transactions` (environment key)

Returns transactions **filtered to the caller's environment only**.

```bash theme={null}
curl https://api.mintfax.com/v1/environment/transactions \
  -H "Authorization: Bearer mfx_test_abc123def456..."
```

A second sandbox environment under the same account has its own `mfx_test_...` key, and that key's `/environment/transactions` call returns only that sibling's activity.

## The deliberate asymmetry

Balance numbers and transaction lists answer different questions.

* The **balance** is the same pool number across sibling environments because the pool really is shared. Two sandbox environments under one account see the same `available` and `held` from `/environment/balance`. That is correct: it is one wallet.
* The **transaction list** from an environment key is filtered to that environment. Two sandbox environments under one account, each calling `/environment/transactions`, see only their own faxes and seed events.

Why filter transactions but share the balance number? Because the balance is a single fact about the account. Transactions describe activity, and you typically do not want one environment's API key to peek at a sibling environment's activity log. If you want the full pool ledger, that is what `/account/transactions` is for - and it requires the account key.

Example:

```
Account: Acme Corp
  Live pool:    9550 credits available
  Sandbox pool: 4550 credits available

  Environment A (sandbox, mfx_test_aaa...)
    /environment/balance      -> { type: "sandbox", available: 4550, held: 450 }
    /environment/transactions -> [tx from Environment A only]

  Environment B (sandbox, mfx_test_bbb...)
    /environment/balance      -> { type: "sandbox", available: 4550, held: 450 }
    /environment/transactions -> [tx from Environment B only]

  Account key (mfx_acct_...)
    /account/balance          -> both pools
    /account/transactions     -> tx from A and B (and any other live environments)
```

## Auto-top-up

Auto-top-up is configured **per balance row** (per pool). When `available` falls below the configured threshold, mintfax tops up:

* **Live pool**: charges the card on file via Stripe for the configured `CreditPackage` and adds those credits.
* **Sandbox pool**: adds simulated credits, no charge.

Configure auto-top-up from the dashboard for each pool independently. The current setting is reflected in the `auto_top_up` block on every balance response.

## Sandbox seed

On registration, the sandbox pool is seeded automatically from a designated `CreditPackage` so you can send sandbox faxes immediately. The seed shows up in the ledger as a `seed`-type transaction. You can exhaust it deliberately to test `insufficient_balance` handling, or reset it from the dashboard.

See [Sandbox - Sandbox balance](/docs/sandbox#sandbox-balance) for the details.

## Insufficient balance

If `available` on the relevant pool is too low to cover the estimated cost of a fax, the API returns HTTP 402 with `insufficient_balance`:

```json theme={null}
{
  "error": "insufficient_balance",
  "message": "Insufficient balance to submit fax",
  "action": "top_up_balance",
  "docs": "https://mintfax.com/docs/errors/insufficient-balance"
}
```

Purchase additional credits for the relevant pool (or enable auto-top-up on the live pool), then retry. In sandbox, refill from the dashboard or wait for auto-top-up if enabled.

## Activation gate

Live operational endpoints require the account to be activated. Until the first successful live credit purchase, calls with an `mfx_live_...` key on `POST /faxes`, `GET /environment/*`, and similar return HTTP 403 with [`account_not_activated`](/docs/errors#account-not-activated). Sandbox is never gated on activation.

See [Activation](/docs/activation) for the buy-as-activation flow.

## Per-page pricing

Credits are deducted per page transmitted. A 3-page fax costs 3x the per-page rate. The per-page rate depends on the destination country and your pricing tier.

## Related pages

* [Keys](/docs/keys) - which key kind to use for `/account/*` vs `/environment/*`
* [Environments](/docs/environments) - sandbox vs live environments
* [Activation](/docs/activation) - what enables live operations
* [Errors](/docs/errors) - `insufficient_balance`, `account_not_activated`, and friends
