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

# Credit Balances

> Read credit-pool countdowns, add units, and pause alert emails

# Credit Balances

If a plan sells a **credit pool** ("5,000 tasks for $99 a month, then $0.03 each"), these endpoints are how you watch it and act on it: two reads for the countdown, and two writes for the moments a human steps in.

The numbers here come from the same balance path the dashboard reads. Your code, your dashboard, and your invoice can't drift apart, because they're all looking at one calculation.

> **Nothing stops at zero.** The pool is a meter, never a breaker. A customer past their pool keeps working and the extra usage bills at the plan's overage rate. If your product should stop serving at zero, your code makes that call: read the balance and decide. The [Credit Pools recipe](/recipes/credit-pools) shows the pattern.

***

## List credit balances

**In plain English:** one row per credit-pool subscription in your organization, emptiest first. Subscriptions whose plan doesn't sell a pool aren't listed at all. No pool means no row, never a fake zero.

**Method & URL:**

```
GET /v1/credit-balances
```

**Auth:** any valid API key for your organization. Reads have no role restriction.

**Query parameters (all optional):**

* `customerId` *(UUID)* - Only pools belonging to this customer.
* `agentId` *(UUID)* - Only pools on subscriptions for this agent.
* `belowPercent` *(number, -100 to 100)* - Only pools with this percent of the pool (or less) still left. `20` is the near-empty cut the dashboard uses; `0` narrows it to pools at or past zero. Negative values are allowed because an overdrawn pool is past zero.

**Example:**

```bash theme={null}
curl https://api.marginfront.com/v1/credit-balances \
  -H "x-api-key: mf_sk_your_key"
```

**What you get back (`200 OK`):**

```json theme={null}
{
  "balances": [
    {
      "subscriptionId": "6f0d...",
      "subscriptionName": "Acme Corp: Pro Plan",
      "customerId": "9a1c...",
      "customerName": "Acme Corp",
      "customerExternalId": "acme-001",
      "agentId": "b2d4...",
      "planId": "c3e5...",
      "planName": "Pro Plan",
      "poolSizeUnits": 5000,
      "consumedUnits": 5300,
      "remainingUnits": 1200,
      "overageInProgressUnits": 300,
      "remainingPercent": 24,
      "overageRatePerUnit": 0.03,
      "periodStart": "2026-08-01T00:00:00.000Z",
      "periodEnd": "2026-09-01T00:00:00.000Z",
      "alertsPaused": false,
      "noActivePeriod": false,
      "implicitFullPool": false
    }
  ],
  "asOf": "2026-08-18T14:00:00.000Z"
}
```

Responses carry a short private cache (up to 30 seconds), and `asOf` tells you exactly when the numbers were computed.

### Every field, in plain English

| Field                    | Type           | What it means                                                                                                                                                                                                                         |
| ------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `subscriptionId`         | UUID           | The subscription this pool belongs to. Use it for the single-balance read and both writes.                                                                                                                                            |
| `subscriptionName`       | string         | The subscription's display name.                                                                                                                                                                                                      |
| `customerId`             | UUID           | The customer's MarginFront UUID.                                                                                                                                                                                                      |
| `customerName`           | string or null | The customer's display name.                                                                                                                                                                                                          |
| `customerExternalId`     | string or null | The customer's ID in YOUR system.                                                                                                                                                                                                     |
| `agentId`                | UUID           | The agent the subscription covers.                                                                                                                                                                                                    |
| `planId`                 | UUID           | The pricing plan that sells the pool.                                                                                                                                                                                                 |
| `planName`               | string or null | That plan's display name.                                                                                                                                                                                                             |
| `poolSizeUnits`          | number         | How many units the plan's pool covers each billing period.                                                                                                                                                                            |
| `consumedUnits`          | number         | Units drawn inside the current period. Top-up blind (see the two-numbers rule below).                                                                                                                                                 |
| `remainingUnits`         | number         | Units left to draw, top-ups included. Goes negative once the pool is overdrawn.                                                                                                                                                       |
| `overageInProgressUnits` | number         | Units already billing as overage this period. This is the invoice's own math.                                                                                                                                                         |
| `remainingPercent`       | number         | `remainingUnits` as a percent of `poolSizeUnits`. Negative once overdrawn.                                                                                                                                                            |
| `overageRatePerUnit`     | number or null | The per-unit overage price, or `null` when the plan's stored tiers can't say.                                                                                                                                                         |
| `periodStart`            | string or null | Current billing period start (ISO 8601). `null` whenever `noActivePeriod` is `true`. Honest absence, never a made-up window.                                                                                                          |
| `periodEnd`              | string or null | Current billing period end. Same null rule.                                                                                                                                                                                           |
| `alertsPaused`           | boolean        | `true` while this subscription's credit-pool alert emails are silenced. Pausing changes emails only; the countdown keeps counting.                                                                                                    |
| `noActivePeriod`         | boolean        | `true` when there's no period counting down right now: the subscription never got a billing period, or the current time falls outside the stored window (like the gap between a period ending and the next day's re-mint). See below. |
| `implicitFullPool`       | boolean        | `true` when no drawable grant exists for the current period slot (it can accompany `noActivePeriod`). The pool reads FULL, because an untouched pool is a full one, not a drained one.                                                |

### The two numbers that can disagree (and why both are right)

`remainingUnits` counts manual top-ups; `overageInProgressUnits` is the invoice's own overage math, which never sees top-ups. A topped-up pool can honestly show `remainingUnits: 1200` AND `overageInProgressUnits: 300` at the same time: the customer has units left, and 300 units are billing as overage this period. The invoice always matches `overageInProgressUnits`, so the bill can never disagree with what this endpoint told you. Count down with `remainingUnits`; talk money with `overageInProgressUnits`.

### Pools with no active period (`noActivePeriod: true`)

Two situations set this flag, and both are normal:

* **Never started.** A subscription created without a start date (for example, one auto-created by the SDK's first event) has no billing period, so its pool has nothing to count down in. Give the subscription a real start date and the countdown begins.
* **Between periods.** A healthy pool whose period just ended sits in a short gap before the next period's grant is minted. During that gap the row reads as a full, untouched pool (`implicitFullPool: true`), not a drained one. A gap between two mint points isn't an empty pool.

Either way the row still appears, with `periodStart` and `periodEnd` as `null`.

***

## Get one subscription's balance

**In plain English:** the same row the list returns, for a single subscription.

**Method & URL:**

```
GET /v1/subscriptions/{subscriptionId}/credit-balance
```

**Auth:** any valid API key for your organization.

**Example:**

```bash theme={null}
curl https://api.marginfront.com/v1/subscriptions/SUBSCRIPTION_ID/credit-balance \
  -H "x-api-key: mf_sk_your_key"
```

**What you get back (`200 OK`):** one balance object, same fields as above.

**Errors:**

* **`404 Not Found`** - The subscription isn't in your organization, or its plan doesn't sell a credit pool. A subscription without a pool has no pool number, never a zero one.

**Using the SDK:**

```typescript theme={null}
const { balances } = await mf.creditBalances.list({ belowPercent: 20 });
const pool = await mf.creditBalances.get("SUBSCRIPTION_ID");
```

**Using MCP:** the `get_credit_balances` tool wraps both reads. Ask "who's running out of credits?"

***

## Add units to a pool (top-up)

**In plain English:** give a customer more units right now, mid-period. Goodwill credit, a side deal, a support resolution. The units never expire.

**Method & URL:**

```
POST /v1/subscriptions/{subscriptionId}/credit-topup
```

**Auth:** an API key or dashboard user with the **owner, admin, or finance** role. Anything less gets a `403`: API-key callers see the allowed roles named in the message; dashboard sessions without the role get a generic forbidden.

**Body:**

* `units` *(number, required)* - How many usage units to add. Must be above zero. These are the same units the plan sells and the invoice bills, never dollars.
* `idempotencyKey` *(string, required, max 255)* - Your own key for this top-up. Can't be blank. Send the same key again and you get the SAME grant back instead of a second one, so a retry can never double-credit a customer. A UUID works.
* `note` *(string, optional, max 1000)* - Why you added the units. Stored on the grant itself, so "why does this customer have extra credits?" is answered from the ledger row.

**Example:**

```bash theme={null}
curl -X POST https://api.marginfront.com/v1/subscriptions/SUBSCRIPTION_ID/credit-topup \
  -H "x-api-key: mf_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "units": 1500,
    "idempotencyKey": "topup-acme-2026-08-goodwill",
    "note": "Goodwill credit for the August incident"
  }'
```

**What you get back (`201 Created`):**

```json theme={null}
{
  "grantId": "e7a9...",
  "subscriptionId": "6f0d...",
  "unitsAdded": 1500,
  "idempotencyKey": "topup-acme-2026-08-goodwill",
  "note": "Goodwill credit for the August incident",
  "createdBy": "api-key:1b7f...",
  "createdAt": "2026-08-18T14:03:00.000Z",
  "created": true,
  "remainingUnits": 1200
}
```

`created: false` means this key was already used and you're looking at the original grant (a replay, not a second credit). `remainingUnits` is the pool's drawable balance after the top-up. `createdBy` records who added the units: the user's ID for dashboard actions, or `api-key:<key id>` for API calls. The grant row itself is the audit trail.

**Rules worth knowing:**

* **Top-ups never expire.** The customer paid for them. Draw order burns the current period's grant first, so a top-up remainder survives into the next period.
* **A top-up raises `remainingUnits`; it does NOT reduce `overageInProgressUnits`.** The invoice's overage math never sees top-ups. Overage already recorded this period stays on the bill.
* **Reuse a key only to retry the exact same top-up.** A key reused with different units, or on a different subscription, is refused with a `409` so a copy-paste slip can't misapply a credit.

**Errors:**

* **`400 Bad Request`** - `units` missing or not above zero, `idempotencyKey` blank, or the subscription's plan sells no credit pool (the message tells you to add a credit-pool pricing strategy first).
* **`403 Forbidden`** - The caller isn't owner, admin, or finance. API-key denials name the allowed roles; dashboard-session denials are a generic forbidden.
* **`404 Not Found`** - Subscription not found in your organization.
* **`409 Conflict`** - The idempotency key was already used with different units, or on a different subscription. The message spells out both the prior use and this request so you can see the mismatch.

***

## Pause or resume alert emails

**In plain English:** quiet the credit-pool emails for one subscription (or turn them back on). You've talked to the customer, the overage is expected, and you don't need four more emails about it.

**Method & URL:**

```
POST /v1/subscriptions/{subscriptionId}/credit-alerts-pause
```

**Auth:** owner, admin, or finance, same as the top-up.

**Body:**

* `paused` *(boolean, required)* - Always send the state you want, never a toggle: `true` silences this subscription's pool emails, `false` turns them back on. Two identical calls leave the subscription in the same place.

**Example:**

```bash theme={null}
curl -X POST https://api.marginfront.com/v1/subscriptions/SUBSCRIPTION_ID/credit-alerts-pause \
  -H "x-api-key: mf_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "paused": true }'
```

**What you get back (`200 OK`):**

```json theme={null}
{ "subscriptionId": "6f0d...", "alertsPaused": true }
```

**What pausing does (and doesn't do):**

* Emails for this subscription stop. That's the whole effect.
* The countdown keeps counting, the balance stays visible everywhere, and the pool still appears on the dashboard's Needs Attention page when it's low (labeled Paused).
* Un-pause mid-period and any alert stages the pool crossed while quiet can fire on the next evaluation. Stages re-arm every new billing period either way.

**Errors:**

* **`403 Forbidden`** - The caller isn't owner, admin, or finance.
* **`404 Not Found`** - Subscription not found in your organization.

***

## The alert emails these writes manage

As a pool drains, MarginFront emails at four stages: **50% used, 80% used, 95% used, and empty** (remaining units at or below zero). One email per stage per billing period, only the highest newly-crossed stage sends, and every stage re-arms when the period rolls over. Recipients: the organization's billing email if one is set, otherwise every owner and finance member. The [Credit Pools recipe](/recipes/credit-pools#the-alert-ladder-50-80-95-empty) covers the ladder in full.

## Related pages

* **[Credit Pools recipe](/recipes/credit-pools)**: the full sell-a-pool walkthrough, including the self-limiting pattern for strict-prepaid products
* **[Pricing Strategies](/api-reference/pricing-strategies)**: creating the pool with the three-number `creditPool` block
* **[Errors](/api-reference/errors)**: the general error-shape reference
