Products, plans and pricing

Pricing in Spondo Billing has three layers:

  • A product is what you sell, such as DocuMind.
  • A product's charges (product attributes in the API) are the ways it can be priced: a platform fee, a charge per page, seats. Each has a pricing type and prices, and can come with credits.
  • A plan is a priced package of one product, such as Growth, holding its own prices for each charge it includes. Orders and checkouts put customers on plans.

Signals drive the usage charges: a usage charge counts the signals whose event name matches its eventName.

You can build all of this in the dashboard (Pricing → Plans & credits) or through the API. This guide uses the API.

Money in pricing

Prices are in integer minor units of their currency: 9900 is $99.00. Fractions are allowed, because usage prices are often below a cent: 0.5 is half a cent. Invoices show amounts in major units, rounded to the cent on each line.

1. Create a product

const product = await spondo.products.create({
  name: "DocuMind",
  externalId: "documind",
  description: "An AI document assistant that reads, reviews and summarises legal documents.",
  active: true,
});
curl https://billing.getspondo.com/api/v1/products \
  --request POST \
  --header "Authorization: Bearer $SPONDO_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{ "name": "DocuMind", "externalId": "documind", "active": true }'

2. Add its charges

Add charges with PUT /products/{id} (or its /external/{externalId} twin). Each entry in productAttributes is one charge: a name and a pricing object. Charges you leave out are untouched, and an entry whose name matches an existing charge updates it.

This gives DocuMind a $99 monthly platform fee that includes 500 credits, and two usage charges paid in credits: one credit per page uploaded and 25 credits per contract reviewed, each 10¢ a credit beyond the allowance.

await spondo.products.update({ externalId: "documind" }, {
  productAttributes: [
    {
      name: "Platform fee",
      pricing: {
        pricingType: "RecurringPerUnit",
        billingFrequency: "Monthly",
        billingType: "Advance",
        pricePoints: [{ currency: "USD", unitPrice: 9900 }],
      },
      creditBenefits: [{ amount: 500 }],
    },
    {
      name: "Documents",
      pricing: {
        pricingType: "UsagePrepaidCredits",
        eventName: "document_uploaded",
        creditCost: 1,
        pricingInput: { path: "pages" },
        overageUnitPrice: 10,
      },
    },
    {
      name: "Contract reviews",
      pricing: { pricingType: "UsagePrepaidCredits", eventName: "contract_reviewed", creditCost: 25, overageUnitPrice: 10 },
    },
  ],
});

The response lists the product's charges with their IDs (productAttributeId), which plans refer to. GET /pricing?productId=prod_… lists them too.

3. Create a plan

A plan names the product and, for each charge it includes, the charge's ID and the plan's own pricing. Plans hold their own copy, so changing a product's charge later never reprices existing plans.

await spondo.plans.create({
  productId: "prod_7Hk2LmQ9xR4tVb1N",
  name: "Growth",
  externalId: "growth-monthly",
  supportedCurrencies: ["USD"],
  isDefault: true,
  attributes: [
    {
      productAttributeId: "0b6f5c2e-3d41-4a8e-9f27-6c1d8e4b7a10",
      pricing: { pricingType: "RecurringPerUnit", billingType: "Advance", pricePoints: [{ currency: "USD", unitPrice: 9900 }] },
      creditBenefits: [{ amount: 500 }],
    },
    {
      productAttributeId: "5e9a2d47-81c3-4f6b-a0d2-3b7e9c1f4a58",
      pricing: { pricingType: "UsagePrepaidCredits", eventName: "document_uploaded", creditCost: 1, pricingInput: { path: "pages" }, overageUnitPrice: 10 },
    },
    {
      productAttributeId: "a3c81f6d-27e9-4b05-8d4a-91f2e6b0c7d3",
      pricing: { pricingType: "UsagePrepaidCredits", eventName: "contract_reviewed", creditCost: 25, overageUnitPrice: 10 },
    },
  ],
});

A customer on Growth who uploads 400 twelve-page documents and has 30 contracts reviewed in a month uses 4,800 + 750 = 5,550 credits. 500 are included, so the month costs $99 + 5,050 × $0.10 = $604.

The default plan (isDefault: true) is the one used when an order or checkout names the product but no plan. If a product has no plans, Spondo Billing makes one from its active charges.

To offer plans in order, as a ladder customers upgrade along, set the upgrade path with PUT /plans/upgrade-path: one group of plan IDs per billing frequency, lowest first.

Archive a plan you no longer sell with PUT /plans/{id} and "status": "archived". Customers already on it keep it.

Pricing types

Every charge's pricing has a pricingType. There are 15, in five kinds:

KindChargedPricing types
RecurringEach billing periodRecurringPerUnit, RecurringGraduated, RecurringVolume, RecurringPercentOfTotal
UsageEach period, from the signals in itUsagePerUnit, UsageGraduated, UsageVolume, UsagePrepaidCredits, UsageBracketedPrepaidCredits, UsageCostPlus
One-timeOnce, on the order's first invoiceOneTimePerUnit
Seat-basedEach period, per seat on the orderSeatBasedPerUnit, SeatBasedGraduated, SeatBasedVolume, SeatBasedPrepaidCredits

The suffix says how the amount is worked out:

  • PerUnit: quantity × unitPrice.
  • Graduated: the quantity is split across tiers, and each tier's units are charged at that tier's price.
  • Volume: every unit is charged at the price of the tier the whole quantity reaches.
  • PrepaidCredits: each unit spends credits; credits beyond what the customer has are charged at the overage price.
pricingTypeFields that matterExample
RecurringPerUnitpricePoints[].unitPrice9900: $99 each month. On an order with quantity 3, $297.
RecurringGraduatedpricePoints[].tiersUp to 10 at $20, then $15: 25 units = 10 × $20 + 15 × $15 = $425 a month.
RecurringVolumepricePoints[].tiersThe same tiers: 25 units = 25 × $15 = $375 a month.
RecurringPercentOfTotalpercentageValue10: 10% of the invoice's other charges, such as a support fee. $636.80 of other charges adds $63.68.
UsagePerUniteventName, pricingInput, unitPrice5¢ a page with pricingInput.path pages: 4,800 pages = $240. With includedQuantity: 1000, $190.
UsageGraduatedeventName, pricingInput, tiersUp to 1,000 pages at 5¢, up to 10,000 at 3¢, then 1¢: 4,800 pages = $50 + $114 = $164.
UsageVolumeeventName, pricingInput, tiersThe same tiers: 4,800 pages × 3¢ = $144.
UsagePrepaidCreditseventName, creditCost, pricingInput, overage price25 credits a contract: 30 reviews spend 750 credits. With 500 included and 10¢ overage, $25.
UsageBracketedPrepaidCreditseventName, pricingInput, creditBrackets, overage priceUp to 10 pages = 1 credit, up to 50 = 3, more = 8: a 12-page document spends 3 credits, a 120-page one 8. A quantity above every bracket uses the highest. With creditCostPerUnit on every bracket instead, they're graduated: up to 10 pages at 1 credit a page, up to 50 at 0.5, then 0.25, so a 120-page document spends 10 + 20 + 17.5 = 47.5 credits.
UsageCostPluscostPlusMultiplier1.3: the customer's traced AI cost for the period plus 30%. $200 of cost = $260.
OneTimePerUnitunitPrice, optional feeType50000: a $500 onboarding fee, once. Its credits are granted once, on activation. feeType labels it setup, onboarding, implementation, migration or custom.
SeatBasedPerUnitunitPrice$30 a seat: 8 seats = $240 a month.
SeatBasedGraduatedtiersUp to 5 seats at $40, then $30: 8 seats = $290 a month.
SeatBasedVolumetiersThe same tiers: 8 seats = 8 × $30 = $240 a month.
SeatBasedPrepaidCreditsunitPrice, creditCost, optional eventName and pricingInput, credit benefits for each seat$30 a seat, each seat with 200 credits a month, and 1 credit per document_uploaded: each person's uploads spend their own seat's credits first, then the customer's shared credits.

The API reference documents every field of each type, with an example.

Fields every type shares

FieldWhat it does
billingFrequencyMonthly (default), Quarterly, Semi-Annually, Annual, or Custom with billingFrequencyCustomMonths. Amounts are per period.
billingTypeAdvance (charged at the start of the period, for the period ahead) or Arrears (at the end, for the period just ended). Everything but usage defaults to Advance; usage, and credit overage, is always billed in arrears.
pricePointsOne entry per currency: { currency, unitPrice, minQuantity?, maxQuantity?, includedQuantity?, tiers? }. An order uses the price point in its currency. Send unitPrice: 0 on types that price by tiers.
tiers{ upperBound, unitAmount, flatAmount, tierBillingType }, sorted by upperBound; leave upperBound out of the last tier. A perUnit tier charges units × unitAmount + flatAmount; a flat tier charges flatAmount once.
minQuantity, maxQuantity, includedQuantityThe quantity billed is the larger of the real quantity and minQuantity, capped at maxQuantity, less includedQuantity. Applies to usage, tiered, and per-unit recurring and seat charges.
eventNameThe signal event a usage charge counts. Required on every usage type except UsageCostPlus.
signalTypeWhat a usage charge bills for: activity (work done, the default) or outcome (a result delivered, such as a resolved ticket). Both bill the same way; it labels the charge.
pricingInput.pathWhere in a signal's data its quantity is, such as pages or usage.tokens. Without it, data.quantity, else 1.

A charge that runs for only part of a period, because it starts, ends or changes mid-period, is prorated by the days it ran.

Credits

Credits let you sell one allowance that every kind of usage draws on. A charge's creditBenefits give the customer credits each period; typically the platform fee carries them:

FieldWhat it does
amountCredits per billing period of the charge. An annual charge's 12,000 credits are 1,000 a month.
recipientorganization (default: one pool for the customer) or seat (the amount for each seat).
isInfiniteTotalUnlimited credits.
allocationCadenceHow often the credits are handed out: monthly (default), quarterly, semiAnnually, annual, billingPeriod (once per billing period of the charge), or oneTime. Each allocation expires at the end of its period.
overageUnitPriceThe price of each credit beyond the allowance, in minor units. overageUnitPrices sets it per currency, such as { "GBP": 8 }. A prepaid-credits charge can set its own, which comes first.
creditsCurrencyIdWhich credit currency the benefit grants; the default is Credits.
creditGrantTimingWhen each allocation is granted: at the start of its period (default), on_invoice_posted, or on_payment. Until then the credits are pending: usage that draws on them is held, and becomes overage if the invoice is voided.
rolloverAmount, rolloverDuration, rolloverDurationUnitHow many unused credits carry into later periods, and for how long: rolloverDurationUnit is days, weeks, months (default) or years. With only a duration, everything left carries over; with no duration, for one allocation period. Rolled-over credits don't roll over again.

The PrepaidCredits pricing types spend them as each signal arrives: each signal of the charge's event spends its quantity × creditCost credits, in the charge's creditsCurrencyId if it names one. Credits come out of the signal's user's seat first (data.external_user_id), then the customer's shared credits, soonest-expiring first; what's left over is overage, billed at the end of the period. Customers, orders and credits covers grants and balances.

Signals and charges

For a signal to be charged:

  1. Its eventName must equal a usage charge's eventName on the customer's plan.
  2. Its quantity is the number at the charge's pricingInput.path in data, else data.quantity, else 1. A missing, negative or non-numeric value counts as 1.
  3. Signals from a customer unit that isn't billable count 0.

The quantity is fixed when the signal arrives, using the charges on the customer's active orders at that moment, so changing an order doesn't reprice signals already received. UsageCostPlus follows cost traces instead of signals.