Seat-Based Pricing
A complete example for a SaaS product that charges per user seat. Covers subscribing with 10 seats, upgrading to 25 seats mid-period with proration, and generating an invoice that shows the prorated credit.
Scenario: Your product charges $49/seat/month. A company starts with 10 seats and grows to 25 seats on day 15 of their billing period.
Step 1: Create a Per-Seat Plan
const BASE_URL = "https://api.bill.sh";
const ADMIN_TOKEN = process.env.ADMIN_TOKEN;
const headers = {
"Authorization": `Bearer ${ADMIN_TOKEN}`,
"Content-Type": "application/json",
};
// Create the product
const productResp = await fetch(`${BASE_URL}/admin/v1/catalog/products`, {
method: "POST", headers,
body: JSON.stringify({
name: "Collaboration Suite",
description: "Seat-based team collaboration platform",
tags: ["saas", "seats"],
}),
});
const product = await productResp.json();
// Create a monthly plan
const planResp = await fetch(`${BASE_URL}/admin/v1/catalog/plans`, {
method: "POST", headers,
body: JSON.stringify({
name: "Teams",
description: "Per-seat monthly billing",
billing_cadence: "Monthly",
trial_days: 0,
}),
});
const plan = await planResp.json();
const planId = plan.id;
// Per-seat SKU: $49/seat = 49_000_000_000_000 nanos
const skuResp = await fetch(`${BASE_URL}/admin/v1/catalog/skus`, {
method: "POST", headers,
body: JSON.stringify({
plan_id: planId,
name: "User Seat",
pricing_model: "PerUnit",
unit_amount_nanos: "49000000000000", // $49.00 per seat
currency: "USD",
// No meter_event_type — quantity is set explicitly on subscription
}),
});
const sku = await skuResp.json();
console.log(`Plan ready: ${planId}, SKU: ${sku.id}`);
Step 2: Subscribe with 10 Seats
// Create the customer
const customerResp = await fetch(`${BASE_URL}/admin/v1/customers`, {
method: "POST", headers,
body: JSON.stringify({
display_name: "Sprocket Co",
email: "billing@sprocket.co",
currency: "USD",
account_type: "Organization",
}),
});
const customer = await customerResp.json();
const customerId = customer.id;
// Subscribe with 10 seats
const subResp = await fetch(`${BASE_URL}/v1/subscriptions`, {
method: "POST",
headers: { ...headers, "Idempotency-Key": `sub-${customerId}-teams-10` },
body: JSON.stringify({
customer_id: customerId,
plan_id: planId,
currency: "USD",
quantity: 10, // 10 seats × $49 = $490/month
}),
});
const subscription = await subResp.json();
const subscriptionId = subscription.id;
console.log(`Subscribed with 10 seats: ${subscriptionId} (${subscription.status})`);
console.log(`Period: ${subscription.period_start} → ${subscription.period_end}`);
Step 3: Upgrade to 25 Seats Mid-Period
On day 15, the customer adds 15 more seats. The billing platform automatically calculates proration:
// Upgrade quantity mid-period
// The platform will:
// 1. Credit the remaining days of 10-seat pricing
// 2. Charge the remaining days of 25-seat pricing
const upgradeResp = await fetch(
`${BASE_URL}/v1/subscriptions/${subscriptionId}/quantity`,
{
method: "POST",
headers: { ...headers, "Idempotency-Key": `upgrade-${subscriptionId}-25seats` },
body: JSON.stringify({
quantity: 25,
prorate: true,
effective_date: "2026-03-15T00:00:00Z", // Day 15 of billing period
}),
}
);
const upgraded = await upgradeResp.json();
console.log(`Upgraded to ${upgraded.quantity} seats`);
console.log("Proration line items:");
for (const line of upgraded.proration_preview ?? []) {
const amountUsd = (BigInt(line.amount_nanos) / BigInt(1e12)).toString();
console.log(` ${line.description}: $${amountUsd}`);
}
// Example output:
// Credit: 10 seats × 16 remaining days: -$26.13
// Charge: 25 seats × 16 remaining days: +$65.32
// Net proration: +$39.19
Step 4: Generate Invoice with Prorated Credit
At the end of the billing period, the invoice will include both the prorated credit and the full new seat charge:
// Trigger billing
await fetch(
`${BASE_URL}/admin/v1/subscriptions/${subscriptionId}/bill`,
{
method: "POST",
headers: { ...headers, "Idempotency-Key": `bill-${subscriptionId}-march` },
}
);
// Find and finalize the Draft invoice
const invoicesResp = await fetch(
`${BASE_URL}/admin/v1/invoices?customer_id=${customerId}&status=Draft`,
{ headers }
);
const invoices = await invoicesResp.json();
const invoiceId = invoices[0].id;
const finalizeResp = await fetch(
`${BASE_URL}/admin/v1/invoices/${invoiceId}/finalize`,
{
method: "POST",
headers: { ...headers, "Idempotency-Key": `finalize-${invoiceId}` },
}
);
const invoice = await finalizeResp.json();
const totalUsd = (BigInt(invoice.total_nanos) / BigInt(1e12)).toString();
console.log(`\nInvoice ${invoice.invoice_number} — $${totalUsd}`);
console.log("Line items:");
for (const line of invoice.line_items) {
const lineUsd = (BigInt(line.amount_nanos) / BigInt(1e12)).toString();
const sign = line.amount_nanos.startsWith("-") ? "" : "+";
console.log(` ${line.description}: ${sign}$${lineUsd}`);
}
// Example output:
// Invoice INV-000023 — $515.19
// 10 seats × 14 days (Mar 1-14): +$219.35
// Credit: 10 seats × 16 days proration: -$26.13
// 25 seats × 16 days (Mar 15-31): +$322.58
// Total: $515.80 (with rounding)
Key Takeaways
- Set
quantityon subscription creation for seat-based plans - Use
prorate: trueon quantity changes to get automatic mid-period credits - The invoice line items clearly show the credit and new charge so customers understand their bill
- For annual plans, consider
billing_cadence: "Annual"with monthly proration to avoid large surprises