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

# Invoices

> Read the bills MarginFront generates

# Invoices

An **invoice** is a finalized bill. The document you send to a customer when it's time to pay. MarginFront generates invoices automatically based on each customer's subscription and logged usage events. The API lets you read those invoices back so you can display them in your own product, sync them to accounting tools, or hand them to your finance team.

Invoice creation happens automatically at the end of each billing period (driven by the subscription's billing cycle). You can also generate a draft invoice on demand for any active subscription using the [generate endpoint](#generate-a-draft-invoice) — useful for mid-cycle billing, one-click "bill now" flows, and previewing what a customer's next invoice will look like.

***

## The endpoints

### List invoices

**Method & URL:**

```
GET /v1/invoices
```

**Query parameters** (all optional):

* `customerExternalId`: filter to a single customer
* `status`: filter by state. Valid values are `draft`, `pending`, `issued`, `paid`, `overdue`, `void`, `refunded` (see [Invoice statuses](#invoice-statuses) below)
* `startDate` / `endDate`: filter by invoice date range
* `limit` / `offset`: pagination

**Example:**

```bash theme={null}
curl "https://api.marginfront.com/v1/invoices?customerExternalId=acme-001" \
  -H "x-api-key: mf_sk_test_..."
```

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

```json theme={null}
{
  "data": [
    {
      "id": "inv_abc123",
      "customerExternalId": "acme-001",
      "status": "issued",
      "total": 429.5,
      "currency": "USD",
      "periodStart": "2026-03-01T00:00:00.000Z",
      "periodEnd": "2026-03-31T23:59:59.999Z",
      "dueDate": "2026-04-30T00:00:00.000Z",
      "createdAt": "2026-04-01T00:00:00.000Z"
    }
  ],
  "hasMore": false
}
```

***

### Read one invoice

**Method & URL:**

```
GET /v1/invoices/{invoiceId}
```

**Returns:** The full invoice including all line items. What each signal cost, per-agent breakdowns, discounts applied, taxes, the works.

**Example:**

```bash theme={null}
curl https://api.marginfront.com/v1/invoices/inv_abc123 \
  -H "x-api-key: mf_sk_test_..."
```

***

### Generate a draft invoice

Turns a subscription's tracked usage into a draft invoice you can preview, edit, or send. Line items and totals are computed server-side from the period's real usage events plus any recurring, seat, or one-time charges on the plan.

**Method & URL:**

```
POST /v1/invoices/generate
```

**Body:**

| Field                | Type   | Required | Description                                                                               |
| -------------------- | ------ | -------- | ----------------------------------------------------------------------------------------- |
| `customerId`         | string | Yes      | The customer being billed (their MarginFront UUID, not the external ID from your system). |
| `subscriptionId`     | string | Yes      | The subscription whose usage you want to turn into an invoice.                            |
| `billingPeriodStart` | string | No       | ISO 8601 date. Defaults to the subscription's current period start.                       |
| `billingPeriodEnd`   | string | No       | ISO 8601 date. Defaults to the subscription's current period end.                         |

When `billingPeriodStart` / `billingPeriodEnd` are omitted, the subscription's current billing period is used — the right answer for "bill now" flows.

**Example:**

```bash theme={null}
curl -X POST https://api.marginfront.com/v1/invoices/generate \
  -H "x-api-key: mf_sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "7a2b3c4d-5e6f-7890-abcd-ef1234567890",
    "subscriptionId": "1f2e3d4c-5b6a-7980-1234-56789abcdef0"
  }'
```

**What you get back (`201 Created`):** the full draft invoice, including line items and totals. The invoice starts in `draft` status — it is not sent until you move it to `issued` (manually, or via the auto-finalize step at the end of the billing period).

**Common errors:**

* **`400 Bad Request`**: `customerId` or `subscriptionId` is missing or not a UUID.
* **`401 Unauthorized`**: API key missing or wrong.
* **`404 Not Found`**: the customer or subscription does not exist in this org, or the customer does not own that subscription.

***

### Send an invoice email

Emails an invoice to the customer with a "Pay Now" button that opens Stripe Checkout pre-filled with the invoice details. Use this after [`/v1/invoices/generate`](#generate-a-draft-invoice) to actually deliver the draft, or to re-send an invoice that has already been issued. The customer's stored email address is used by default — pass `recipientEmail` to override (for example, to route the invoice to a different billing contact).

**Method & URL:**

```
POST /v1/invoices/{invoiceId}/send
```

**Body** (all fields optional — omit the body entirely to send to the customer's stored email with auto-generated subject and body):

| Field            | Type   | Required | Description                                                                                               |
| ---------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------- |
| `recipientEmail` | string | No       | Override the destination address. Falls back to the customer's stored email.                              |
| `subject`        | string | No       | Custom subject line. Default: `Invoice {number} from {your business name}`.                               |
| `message`        | string | No       | Optional note shown in a callout above the invoice details (for example, "Card on file will be charged"). |

**Example:**

```bash theme={null}
curl -X POST https://api.marginfront.com/v1/invoices/inv_abc123/send \
  -H "x-api-key: mf_sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "recipientEmail": "billing@customer.com",
    "subject": "May usage invoice — auto-charge in 5 days",
    "message": "Card on file will be charged automatically."
  }'
```

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

```json theme={null}
{
  "success": true,
  "message": "Invoice email sent successfully",
  "emailId": "msg_88c9a928",
  "recipientEmail": "billing@customer.com"
}
```

`emailId` is the provider's message ID for delivery tracking. `recipientEmail` echoes back the address the email was actually sent to (after applying any override).

**Side effect — auto-finalize:** if the invoice was still in `draft` status when this call landed, sending it auto-finalizes the status from `draft` to `issued`. This matches the behavior of the dashboard's Send button and the end-of-period auto-finalize step. No separate "finalize" call is needed first.

**After payment:** once the customer clicks the Stripe Checkout link in the email and pays, the invoice status flips to `paid` automatically via webhook. You do not need to poll or make a follow-up call to confirm.

**Common errors:**

* **`400 Bad Request`**: the invoice cannot be sent (for example, it is already `paid` or `void`).
* **`401 Unauthorized`**: API key missing or wrong.
* **`404 Not Found`**: no invoice with that ID exists in this org.

***

## Invoice statuses

MarginFront invoices move through one of seven statuses over their lifetime. Every invoice in the API has its `status` field set to exactly one of these.

| Status     | Meaning                                                                                                    |
| ---------- | ---------------------------------------------------------------------------------------------------------- |
| `draft`    | Still being built. Not yet sent. Amounts can still change. Does not count as billed revenue.               |
| `pending`  | Finalized and queued to send. Sitting in the outbox waiting for the send step.                             |
| `issued`   | Sent to the customer. Awaiting payment. Counts as billed revenue.                                          |
| `paid`     | Customer paid in full. Counts as collected revenue.                                                        |
| `overdue`  | Past the due date without full payment. Still counts as billed revenue; payment is late.                   |
| `void`     | Canceled. Treated as if it never happened for billing purposes. Does not count toward billed or collected. |
| `refunded` | Was paid, then fully refunded. Counts as neither billed nor collected once it lands here.                  |

The natural "happy path" is `draft` → `pending` → `issued` → `paid`. Most invoices go through exactly those four.

### Refund flow

Refunds are recorded against a payment, not directly against the invoice. When you create a refund using `POST /v1/invoices/:invoiceId/payments/:paymentId/refund`, MarginFront records the refund and, if the refund covers the entire invoice, moves the invoice from `paid` to `refunded`. Partial refunds leave the invoice in `paid` and just reduce the net collected amount.

Once an invoice is `refunded`:

* It is excluded from collected revenue totals.
* It is excluded from billed revenue totals (as if it had never been invoiced).
* The underlying payment and refund records are preserved for audit.

The dashboard and SDK analytics endpoints already handle `refunded` correctly. If you are rolling your own accounting sync, treat `refunded` invoices the same way you would treat `void` invoices for revenue-recognition purposes.

***

## Common errors

* **`401 Unauthorized`**: API key missing or wrong.
* **`404 Not Found`**: no invoice with that ID exists in this org.

***

## Using the Node SDK

```js theme={null}
// List recent invoices for a customer
const { data } = await mf.invoices.list({
  customerExternalId: "acme-001",
});

// Fetch a specific invoice
const invoice = await mf.invoices.get("inv_abc123");

console.log(`Amount due: $${invoice.total} ${invoice.currency}`);

// Generate a draft invoice from a subscription's current billing period
const draft = await mf.invoices.generate({
  customerId: "7a2b3c4d-5e6f-7890-abcd-ef1234567890",
  subscriptionId: "1f2e3d4c-5b6a-7980-1234-56789abcdef0",
});

console.log(`Draft ${draft.invoiceNumber}: $${draft.totalAmount}`);

// Email the draft to the customer. If the invoice is still in draft status,
// this also auto-finalizes it to "issued" as a side effect. The customer's
// stored email is used by default.
const sent = await mf.invoices.send(draft.id);
console.log(`Sent to ${sent.recipientEmail} (message id: ${sent.emailId})`);

// Override recipient + subject + add a custom note
await mf.invoices.send("inv_abc123", {
  recipientEmail: "billing@customer.com",
  subject: "May usage invoice — auto-charge in 5 days",
  message: "Card on file will be charged automatically.",
});
```

***

## Analytics vs invoices: which do I want?

Quick disambiguation since both expose "cost" numbers:

| Use analytics                                        | Use invoices                                       |
| ---------------------------------------------------- | -------------------------------------------------- |
| "How much has this customer used so far this month?" | "What's the final bill I'm sending them?"          |
| Live projections, dashboards                         | Accounting, A/R, customer portals                  |
| Raw usage data, no taxes/discounts                   | Finalized totals with taxes, discounts, prorations |
| Current period, real-time                            | Completed billing periods                          |

If the number needs to match what lands on the customer's credit card, use invoices. If it's a "heads up, here's what you're using" display, use analytics.
