> ## 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 Pools

> Sell a bundle of usage upfront, watch it count down, and read the same balance from your own code

# Sell a Credit Pool (and Stay Aligned With Your Own Bill)

You sell an AI agent. You want to price it the way customers like to buy: "5,000 tasks for \$99 a month, then 3 cents per task after that."

That pricing shape is a **credit pool**. This guide walks the whole loop, start to finish:

1. Create the pool pricing
2. Connect it to your agent and a customer
3. Record usage from your code
4. Read the countdown from your code, so your product and your invoice always say the same thing

One rule to hold onto the whole way through, because everything else follows from it:

> **Nothing stops when the pool hits zero.** MarginFront is the meter, never the breaker. A customer who uses up their pool keeps working, and the extra usage bills at your overage rate. If you want usage to stop at zero, that decision belongs in your code, and the last section shows you exactly how.

## Before you start

You need:

* A secret API key (`mf_sk_...`) from **Build > API keys** in the dashboard
* The SDK installed if you want the SDK examples: `npm install @marginfront/sdk`

```typescript theme={null}
import { MarginFrontClient } from "@marginfront/sdk";

const mf = new MarginFrontClient(process.env.MF_API_SECRET_KEY);
```

A note on order: usage tracking alone needs no setup (agents, signals, and customers auto-create on the first event). Credit pools are different. The pool prices a **signal**, so the agent and the signal have to exist before you can create the pool. That's why this guide creates them explicitly in steps 1 and 2.

***

## Step 1: Create the agent and the signal

The **agent** is your product. The **signal** is the unit you sell (the word your customer would use: tasks, reports, messages).

```bash theme={null}
curl -X POST https://api.marginfront.com/v1/agents \
  -H "x-api-key: mf_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Task Runner", "agentCode": "task-runner" }'
```

```bash theme={null}
curl -X POST https://api.marginfront.com/v1/signals \
  -H "x-api-key: mf_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "tasks", "agentId": "AGENT_ID" }'
```

Save the `id` from each response. The next steps need them. (You can also create both in the dashboard, or ask the MCP server to do it.)

## Step 2: Create the plan and the credit pool

A **plan** is the container ("Pro Plan"). The **credit pool** is one pricing rule inside it. You describe the pool with exactly three numbers:

| Number        | Meaning                                                    | Our example |
| ------------- | ---------------------------------------------------------- | ----------- |
| `poolSize`    | How many units the pool covers each billing cycle          | 5000        |
| `poolPrice`   | The flat fee charged every cycle, whether or not it's used | 99          |
| `overageRate` | The per-unit price after the pool runs out                 | 0.03        |

First the plan:

```bash theme={null}
curl -X POST https://api.marginfront.com/v1/pricing-plans \
  -H "x-api-key: mf_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Pro Plan" }'
```

Then the pool. One call, three numbers. Pick whichever surface you're using:

**SDK:**

```typescript theme={null}
const strategy = await mf.pricingStrategies.createCreditPool("PLAN_ID", {
  name: "Task Pool",
  agentId: "AGENT_ID",
  signalId: "SIGNAL_ID",
  poolSize: 5000,
  poolPrice: 99,
  overageRate: 0.03,
});
```

**curl:**

```bash theme={null}
curl -X POST https://api.marginfront.com/v1/pricing-plans/PLAN_ID/pricing-strategies \
  -H "x-api-key: mf_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "name": "Task Pool",
      "agentId": "AGENT_ID",
      "chargeType": "usage",
      "pricingModel": "credit_pool",
      "signalId": "SIGNAL_ID",
      "creditPool": { "poolSize": 5000, "poolPrice": 99, "overageRate": 0.03 }
    }
  ]'
```

(The body is an array because this endpoint creates strategies in bulk. One strategy still goes in an array.)

**MCP:** ask your AI assistant to run `create_pricing_strategy` with `pricingModel: "credit_pool"` and the same `creditPool` block. In plain English: "Add a credit pool to the Pro Plan: 5,000 tasks for \$99 a month, then 3 cents each."

The server compiles those three numbers into the stored two-tier shape for you. Two rules the server enforces, with plain error messages when you hit them:

* **One pool per plan.** A second active credit pool on the same plan is rejected. Put a second pool on its own plan.
* **No minimum commitment on a pool.** The pool fee already is the minimum the customer pays every cycle.

## Step 3: Link the plan to the agent

```bash theme={null}
curl -X POST https://api.marginfront.com/v1/pricing-plans/PLAN_ID/agents \
  -H "x-api-key: mf_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "agentId": "AGENT_ID" }'
```

Or with the SDK: `await mf.pricingPlans.linkAgent("PLAN_ID", "AGENT_ID")`.

## Step 4: Create the customer

```typescript theme={null}
const customer = await mf.customers.create({
  name: "Acme Corp",
  externalId: "acme-001",
  email: "billing@acme.com",
});
```

`externalId` is the ID from your own system. It's how your usage events find this customer later.

## Step 5: Create the subscription

The subscription ties the customer to the plan and starts the clock. The billing period comes from `startDate`, and the pool's countdown lives inside that period.

```bash theme={null}
curl -X POST https://api.marginfront.com/v1/subscriptions \
  -H "x-api-key: mf_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp: Pro Plan",
    "planId": "PLAN_ID",
    "agentId": "AGENT_ID",
    "customerId": "CUSTOMER_ID",
    "startDate": "2026-08-01T00:00:00Z",
    "billingCycle": "monthly",
    "billingModel": "usage"
  }'
```

The response includes `currentBillingPeriodStart` and `currentBillingPeriodEnd`. That window is the pool's period: 5,000 fresh units each cycle, unused units don't roll over, and the flat \$99 bills every cycle either way.

The SDK doesn't create subscriptions today (it reads them). Use curl, the dashboard, or the MCP `create_subscription` tool for this step.

## Step 6: Record usage

This part is the normal MarginFront integration. Fire one event per business outcome, with `quantity` counting the signal's units:

```typescript theme={null}
await mf.usage.record({
  customerExternalId: "acme-001",
  agentCode: "task-runner",
  signalName: "tasks",
  model: "claude-sonnet-4",
  modelProvider: "anthropic",
  inputTokens: 1200,
  outputTokens: 300,
  quantity: 1, // one task done
});
```

Each event's quantity draws down the pool as it lands. Record 250 tasks and the pool reads 4,750 left. See [Tracking Events](/sdk/tracking-events) for the full event guide.

## Step 7: Read the balance from your own code

This is the step that keeps your product and your bill telling the same story. The dashboard, the API, the SDK, and the MCP server all read one shared balance path, so the number your code sees is the number the invoice math uses.

**SDK:**

```typescript theme={null}
// All pools, emptiest first
const { balances } = await mf.creditBalances.list();

// One subscription
const pool = await mf.creditBalances.get("SUBSCRIPTION_ID");
console.log(`${pool.remainingUnits} of ${pool.poolSizeUnits} units left`);

// Only the pools that need a human: 20% or less remaining
const lowPools = await mf.creditBalances.list({ belowPercent: 20 });
```

**curl:**

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

curl https://api.marginfront.com/v1/subscriptions/SUBSCRIPTION_ID/credit-balance \
  -H "x-api-key: mf_sk_your_key"
```

**MCP:** ask "how many units does Acme have left?" and the assistant runs `get_credit_balances`.

Reads are open to any valid key for your organization. The full field list lives in the [Credit Balances API reference](/api-reference/credit-balances).

***

## The two numbers (read this once, save yourself a support ticket)

Every balance row carries two numbers that can disagree, and both are right:

* **`remainingUnits`** is the countdown, and it counts your manual top-ups.
* **`overageInProgressUnits`** is the invoice's own overage math, which never sees top-ups, so the bill can never disagree with what this field told you.

Example: Acme burns through all 5,000 units, then uses 300 more, and you top them up with 1,500 goodwill units. The balance now honestly reads `remainingUnits: 1200` AND `overageInProgressUnits: 300`. The customer has units to use, and 300 units are billing as overage this period. Both true at once. When you show a customer what they'll owe, use `overageInProgressUnits`. When you show them what's left, use `remainingUnits`.

## What zero (and negative) means

`remainingUnits` goes negative once the pool is overdrawn. Nothing else changes:

* The customer's requests keep working. MarginFront never blocks, throttles, or drops anything.
* Every unit past the pool bills at your `overageRate` when the period's invoice is generated.
* The pool refills at the next billing period.

MarginFront is the meter, never the breaker. If your product promises "strict prepaid, no surprises," the last section of this guide is yours.

## The alert ladder: 50%, 80%, 95%, empty

As a pool drains, MarginFront emails your team at four stages: **50% used, 80% used, 95% used, and empty** (empty means remaining units at or below zero).

How the emails behave:

* **Who gets them:** your organization's billing email if one is set (Settings > Billing), otherwise every member with the owner or finance role.
* **One email per stage per period.** A pool that crosses 80% today won't re-email 80% tomorrow. Every stage re-arms when the next billing period starts.
* **Only the highest new stage emails.** A pool that jumps from fresh to 97% in one burst gets one 95% email, not three.
* **Topping up un-crosses stages, but never rewinds an email.** Add units and the used percent drops. A stage that already emailed this period stays quiet even if the pool drains back through it. Every stage re-arms when the next billing period starts.
* **Pause and resume anytime.** Pausing silences the emails for one subscription and nothing else: the countdown keeps counting and the balance stays visible everywhere. Pause from the dashboard or `POST /v1/subscriptions/:id/credit-alerts-pause` with `{"paused": true}`. Send `{"paused": false}` to turn them back on; stages the pool crossed while paused can then fire on the next evaluation.

The emails link to the dashboard's Needs Attention page, which brings us to:

## When a pool runs dry: the Needs Attention flow

The dashboard's **Needs Attention** page (the link inside every pool email) lists every pool that's empty or under 20% remaining, with an honest banner: "Working as designed: nothing is blocked, the overage keeps billing." Paused-alert pools still appear there, labeled Paused, because pausing emails doesn't hide the situation.

Each row has a **Top up or pause** button with the two resolutions a human actually reaches for:

* **Top up**: add units to the pool right now.
* **Pause or resume alerts**: quiet the emails for a customer you've already talked to.

The same section, with the same actions, lives in **Money > Plans** under Credit Pools. Both roads resolve identically.

## Topping up

A top-up adds units to a customer's pool mid-period. From the dashboard it's a button; from the API:

```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"
  }'
```

Three things worth knowing:

* **`idempotencyKey` is required, and it's your safety net.** Send the same key twice (a retry, a double-click) and the SAME grant comes back instead of a second one, so a customer can never be double-credited. Reuse a key with different units, or on a different subscription, and you get a 409 telling you so. Any string unique to the top-up works; a UUID is fine.
* **Top-ups never expire.** The customer paid for those units. Draw order burns the current period's grant first, so a top-up remainder survives into the next period.
* **A top-up doesn't shrink overage already on the books.** It raises `remainingUnits` going forward. The overage the invoice already counted stays counted (that's the two-numbers rule from above).

Top-ups and alert pausing need a key or dashboard user with the **owner, admin, or finance** role. Anything less gets a 403 (an API-key caller sees the allowed roles named in the message; a dashboard session without the role gets a generic forbidden).

## If your product sells strict prepaid: the self-limiting pattern

Maybe your promise to customers is "when the credits run out, the agent stops." MarginFront won't stop it for you, on purpose: a billing hiccup should never take your product down, and plenty of businesses want overage revenue. **Your code is the enforcement point.** The balance read exists exactly so you can make that call:

```typescript theme={null}
async function runTask(customerExternalId: string, subscriptionId: string) {
  const pool = await mf.creditBalances.get(subscriptionId);

  if (pool.remainingUnits <= 0) {
    // YOUR product's decision, enforced by YOUR code.
    // MarginFront reported the number; it did not block anything.
    return {
      ok: false,
      message: "You're out of credits. Top up to keep going.",
    };
  }

  const result = await doTheActualWork();

  await mf.usage.record({
    customerExternalId,
    agentCode: "task-runner",
    signalName: "tasks",
    model: "claude-sonnet-4",
    modelProvider: "anthropic",
    inputTokens: result.inputTokens,
    outputTokens: result.outputTokens,
    quantity: 1,
  });

  return { ok: true, result };
}
```

Being honest about this pattern's edges:

* The balance read is a point-in-time snapshot with a short cache (up to 30 seconds). Requests already in flight when the pool empties will land and bill as overage. For most products a few units of slack is nothing; if it matters, check less often and keep your own local count between checks.
* Checking before every single request adds a network read to your hot path. A common middle ground: check every Nth request, or only once `remainingUnits` drops under 10% of `poolSizeUnits`.
* If you block at zero, tell the customer how to get going again (a top-up on your end, or their next billing period).

## Next steps

* **[Credit Balances API reference](/api-reference/credit-balances)**: every field on the balance row, both write endpoints, all the error cases
* **[Pricing Strategies](/api-reference/pricing-strategies)**: the stored tier shape behind the three numbers, and every validation rule
* **[Tracking Events](/sdk/tracking-events)**: the full guide to recording usage
