Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 quantity on subscription creation for seat-based plans
  • Use prorate: true on 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