Guide
Bill without double billing
Raise exactly one invoice per customer per billing period, however many times your job runs or retries.
Scope: invoices:write.
Choose a deterministic externalRef#
Build externalRef from what you're billing: your customer id and the period, such as ws_42-2026-11. The same customer and month always give the same reference. Flux never raises two invoices with one reference, permanently, so a rerun or a retry returns the invoice that already exists.
| Billing | Example externalRef |
|---|---|
| Monthly subscription | ws_42-2026-11 |
| Quarterly subscription | ws_42-2026-Q4 |
| One-off setup fee | ws_42-setup |
| Usage for a period | ws_42-usage-2026-11 |
At most 100 characters.
Raise the invoice#
// Raises the invoice for one customer and month. Safe to run any number of times.
export async function billMonth(customer, year, month) {
const period = `${year}-${String(month).padStart(2, "0")}`; // "2026-11"
const externalRef = `ws_${customer.workspaceId}-${period}`; // "ws_42-2026-11"
const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
const res = await fetch(`${process.env.FLUX_BASE_URL}/api/v1/invoices`, {
method: "POST",
headers: {
Authorization: `ApiKey ${process.env.FLUX_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `invoice-${externalRef}`,
},
body: JSON.stringify({
externalRef,
customerId: customer.fluxId,
currency: "NGN",
dueDate: `${period}-15`,
periodStart: `${period}-01`,
periodEnd: `${period}-${lastDay}`,
invoiceType: "subscription",
lines: [
{ serviceId: 7, quantity: customer.entityCount, unitPrice: 250000, detail: `${customer.entityCount} entities` },
],
}),
});
if (res.status === 201) return { invoice: await res.json(), created: true };
if (res.status === 200) return { invoice: await res.json(), created: false }; // already raised
throw new Error(`${res.status} ${await res.text()}`);
}import calendar
import os
import requests
def bill_month(customer, year, month):
"""Raises the invoice for one customer and month. Safe to run any number of times."""
period = f"{year}-{month:02d}" # "2026-11"
external_ref = f"ws_{customer['workspace_id']}-{period}" # "ws_42-2026-11"
last_day = calendar.monthrange(year, month)[1]
res = requests.post(
f"{os.environ['FLUX_BASE_URL']}/api/v1/invoices",
headers={
"Authorization": f"ApiKey {os.environ['FLUX_API_KEY']}",
"Idempotency-Key": f"invoice-{external_ref}",
},
json={
"externalRef": external_ref,
"customerId": customer["flux_id"],
"currency": "NGN",
"dueDate": f"{period}-15",
"periodStart": f"{period}-01",
"periodEnd": f"{period}-{last_day}",
"invoiceType": "subscription",
"lines": [
{"serviceId": 7, "quantity": customer["entity_count"], "unitPrice": 250000,
"detail": f"{customer['entity_count']} entities"},
],
},
timeout=30,
)
if res.status_code in (200, 201):
return res.json(), res.status_code == 201 # (invoice, created)
raise RuntimeError(f"{res.status_code} {res.text}")- 201: a new invoice. Save its
idagainst your period. - 200: one already exists for this reference. Flux returns it as it is now and changes nothing, even if your amounts differ. If they do, a person needs to fix the invoice in Flux.
Lines and amounts#
- For per-unit pricing, send
quantityandunitPrice. Flux multiplies and rounds to 2 decimals, and the line reads “Subscription — 3 entities (3 × 250,000.00)”. - For a fixed amount, send
amount. - You can't send a total. Flux adds the lines, takes off
discount, and applies the organization's VAT.
Remember approval#
A new invoice is awaiting_approval. The customer gets nothing until someone approves it in Flux. Listen for invoice.approved or invoice.issued to know when it has gone out, and for invoice.returned if a reviewer sends it back.
A returned invoice is fixed and resubmitted in Flux; don't raise a new one. If an invoice is voided and you need to bill the period again, use a new reference, such as ws_42-2026-11-r2.