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:
| Kind | Charged | Pricing types |
|---|---|---|
| Recurring | Each billing period | RecurringPerUnit, RecurringGraduated, RecurringVolume, RecurringPercentOfTotal |
| Usage | Each period, from the signals in it | UsagePerUnit, UsageGraduated, UsageVolume, UsagePrepaidCredits, UsageBracketedPrepaidCredits, UsageCostPlus |
| One-time | Once, on the order's first invoice | OneTimePerUnit |
| Seat-based | Each period, per seat on the order | SeatBasedPerUnit, 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.
pricingType | Fields that matter | Example |
|---|---|---|
RecurringPerUnit | pricePoints[].unitPrice | 9900: $99 each month. On an order with quantity 3, $297. |
RecurringGraduated | pricePoints[].tiers | Up to 10 at $20, then $15: 25 units = 10 × $20 + 15 × $15 = $425 a month. |
RecurringVolume | pricePoints[].tiers | The same tiers: 25 units = 25 × $15 = $375 a month. |
RecurringPercentOfTotal | percentageValue | 10: 10% of the invoice's other charges, such as a support fee. $636.80 of other charges adds $63.68. |
UsagePerUnit | eventName, pricingInput, unitPrice | 5¢ a page with pricingInput.path pages: 4,800 pages = $240. With includedQuantity: 1000, $190. |
UsageGraduated | eventName, pricingInput, tiers | Up to 1,000 pages at 5¢, up to 10,000 at 3¢, then 1¢: 4,800 pages = $50 + $114 = $164. |
UsageVolume | eventName, pricingInput, tiers | The same tiers: 4,800 pages × 3¢ = $144. |
UsagePrepaidCredits | eventName, creditCost, pricingInput, overage price | 25 credits a contract: 30 reviews spend 750 credits. With 500 included and 10¢ overage, $25. |
UsageBracketedPrepaidCredits | eventName, pricingInput, creditBrackets, overage price | Up 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. |
UsageCostPlus | costPlusMultiplier | 1.3: the customer's traced AI cost for the period plus 30%. $200 of cost = $260. |
OneTimePerUnit | unitPrice, optional feeType | 50000: a $500 onboarding fee, once. Its credits are granted once, on activation. feeType labels it setup, onboarding, implementation, migration or custom. |
SeatBasedPerUnit | unitPrice | $30 a seat: 8 seats = $240 a month. |
SeatBasedGraduated | tiers | Up to 5 seats at $40, then $30: 8 seats = $290 a month. |
SeatBasedVolume | tiers | The same tiers: 8 seats = 8 × $30 = $240 a month. |
SeatBasedPrepaidCredits | unitPrice, 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
| Field | What it does |
|---|---|
billingFrequency | Monthly (default), Quarterly, Semi-Annually, Annual, or Custom with billingFrequencyCustomMonths. Amounts are per period. |
billingType | Advance (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. |
pricePoints | One 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, includedQuantity | The 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. |
eventName | The signal event a usage charge counts. Required on every usage type except UsageCostPlus. |
signalType | What 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.path | Where 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:
| Field | What it does |
|---|---|
amount | Credits per billing period of the charge. An annual charge's 12,000 credits are 1,000 a month. |
recipient | organization (default: one pool for the customer) or seat (the amount for each seat). |
isInfiniteTotal | Unlimited credits. |
allocationCadence | How 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. |
overageUnitPrice | The 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. |
creditsCurrencyId | Which credit currency the benefit grants; the default is Credits. |
creditGrantTiming | When 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, rolloverDurationUnit | How 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:
- Its
eventNamemust equal a usage charge'seventNameon the customer's plan. - Its quantity is the number at the charge's
pricingInput.pathindata, elsedata.quantity, else 1. A missing, negative or non-numeric value counts as 1. - 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.